简单分析React中的EffectList

React中,会遍历EffectList来执行节点操作、生命周期方法、Effect方法,可以把EffectList比作圣诞树上挂的彩灯,而这颗圣诞树就是Fiber树。

为什么会存在EffectList呢?打个比方来说,一颗Fiber树中有一些Fiber节点需要执行componentDidMount方法,如果在Fiber树构建完成后,再遍历一次Fiber树,找到需要执行componentDidMount方法的Fiber节点,这是非常低效的。

而EffectList就解决了这个问题,在Fiber树构建过程中,每当一个Fiber节点的flags字段不为NoFlags时(代表需要执行副作用),就把该Fiber节点添加到EffectList,在Fiber树构建完成后,由Fiber节点串成的彩灯也构建完成了,这样仅仅需要遍历彩灯就行了。

EffectList的收集

EffectList是一个单向链表,firstEffect代表链表中的第一个Fiber节点,lastEffect代表链表中的最后一个Fiber节点。

Fiber树的构建是深度优先的,也就是先向下构建子级Fiber节点,子级节点构建完成后,再向上构建父级Fiber节点,所以EffectList中总是子级Fiber节点在前面。

Fiber节点构建完成的操作执行在completeUnitOfWork方法,在这个方法里,不仅会对节点完成构建,也会将有flags的Fiber节点添加到EffectList。

简化代码如下。

function completeUnitOfWork(unitOfWork: Fiber): void {
 let completedWork = unitOfWork;
 do {
  const current = completedWork.alternate;
  const returnFiber = completedWork.return;

  let next= completeWork(current, completedWork, subtreeRenderLanes);

  // effect list构建
  if (
   returnFiber !== null &&
   (returnFiber.flags & Incomplete) === NoFlags
  ) {
   // 层层拷贝
   if (returnFiber.firstEffect === null) {
    returnFiber.firstEffect = completedWork.firstEffect;
   }
   if (completedWork.lastEffect !== null) {
    // 说明当前节点是兄弟节点,子节点有effect,已经给returnFiber.lastEffect赋值过了
    if (returnFiber.lastEffect !== null) {
     // 连接兄弟节点的effect
     returnFiber.lastEffect.nextEffect = completedWork.firstEffect;
    }
    returnFiber.lastEffect = completedWork.lastEffect;
   }

   const flags = completedWork.flags;

   // 该fiber节点有effect
   if (flags > PerformedWork) {
    // 当前节点有effect连接上effect list
    if (returnFiber.lastEffect !== null) {
     returnFiber.lastEffect.nextEffect = completedWork;
    } else {
     // returnFiber没有firstEffect的情况是第一次遇见有effect的节点
     returnFiber.firstEffect = completedWork;
    }
    returnFiber.lastEffect = completedWork;
   }
  }

  // 兄弟元素遍历再到返返回父级
  const siblingFiber = completedWork.sibling;
  if (siblingFiber !== null) {
   workInProgress = siblingFiber;
   return;
  }
  completedWork = returnFiber;
  workInProgress = completedWork;
 } while (completedWork !== null);
}

EffectList实际是像冒泡一样,一层一层不断向上层收集,从第一个有flags的节点开始记录,每层的新节点都会将上一个节点的firstEffectlastEffect拷贝到自身身上,再供上层节点再次拷贝。

如以下结构,假如每一个div都有flags

<div id="1">
 <div id="4"/>
 <div id="2">
  <div id="3"/>
 </div>
</div>

最终形成的EffectList为

firstEffect => div4
lastEffect => div1

因为Fiber树的构建深度优先,所有div4先完成completeWork,构建firstEffect

EffectList遍历是从firstEffect开始,通过每一个节点的nextEffect找到下一个节点。

firstEffect => div4
div4.nextEffect => div3
div3.nextEffect => div2
div2.nextEffect => div1

初次Render时的EffectList

在React中,会对初次Mount有一个性能优化,其中的Fiber节点的flags不会包含placement,对应的DOM节点不会遍历加入DOM树,而是在创建DOM节点时就已经加入DOM树了,只有rootFiber节点FiberRootNodeflags会包含placement

EffectList是不会包含root节点的,所以需要将root节点也添加到EffectList,这样才会正确的执行placement,让DOM树在页面呈现 。

 let firstEffect;
 // 把根节点finishedWork也连接进去
 if (finishedWork.flags > PerformedWork) {
  if (finishedWork.lastEffect !== null) {
   finishedWork.lastEffect.nextEffect = finishedWork;
   firstEffect = finishedWork.firstEffect;
  } else {
   firstEffect = finishedWork;
  }
 } else {
  // 根节点没有effect.
  firstEffect = finishedWork.firstEffect;
 }

EffectList的遍历

EffectList的主要是用于Layout阶段生命周期方法的执行和DOM的操作。

// 处理getSnapshotBeforeUpdate,调度useEffect
nextEffect = firstEffect;
do {
 commitBeforeMutationEffects();
} while (nextEffect !== null);
// DOM操作
nextEffect = firstEffect;
do {
 commitMutationEffects(root, renderPriorityLevel);
} while (nextEffect !== null);
// 生命周期方法的执行
nextEffect = firstEffect;
do {
 commitLayoutEffects(root, lanes);
} while (nextEffect !== null);

在这Layout阶段的这3个方法里,会遍历nextEffect,每执行完一个,就重新指向firstEffect。Layout阶段具体操作就不细讲了。

总结

EffectList不是全局变量,只是在Fiber树创建过程中,一层层向上收集有effect的Fiber节点,最终的root节点就会收集到所有有effect到Fiber节点,我们就把这条包含effect节点的链表叫做EffectList。

由于收集的过程是深度优先,子级会先被收集,所以遍历的时候也会先操作子级,所以如果有面试官问子级和父级的生命周期或者useEffect谁先执行,就很清楚的知道会先执行子级操作了。

以上就是简单分析React中的EffectList的详细内容,更多关于React中的EffectList的资料请关注我们其它相关文章!

(0)

相关推荐

  • React+Ant Design开发环境搭建的实现步骤

    基础知识 1.使用脚手架创建项目并启动 1.1 安装脚手架: npm install -g create-react-app 1.2 使用脚手架创建项目: create-react-app antd-start-demo antd-start-demo为项目名. 1.3 启动 npm start 2.npm转换为yarn 2.1 安装yarn: npm install -g yarn ​ 2.2 获取yarn当前的镜像源: yarn config get registry ​ 2.3 设置为淘宝

  • 使用 React 和 Threejs 创建一个VR全景项目的过程详解

    最近我在学习使用 React 配合 Three.js 来搭建一个可以浏览720全景图片的项目 实现的是加载一张 2:1 的720全景 分享一下我的创建过程 一.搭建框架并安装需要的插件 npx create-react-app parano // 创建一个 React 项目 npm install -S typescript // 安装 typescript,这个是类型辅助插件,与全景项目关系不大 npm install -S @types/three // 安装 typescript 支持的

  • 聊一聊我对 React Context 的理解以及应用

    前言 Context被翻译为上下文,在编程领域,这是一个经常会接触到的概念,React中也有. 在React的官方文档中,Context被归类为高级部分(Advanced),属于React的高级API,但官方并不建议在稳定版的App中使用Context. The vast majority of applications do not need to use content. If you want your application to be stable, don't use context

  • Vite搭建React项目的方法步骤

    前言 日常放鸽,火钳刘明 这是一个基于 vite 搭建的 React 的项目,开发体验非常棒. 创建一个 Vite 项目 yarn create @vitejs/app 如上图,选择了 react-ts 预设模板,如果出现下图一样的工程 yarn // 安装依赖 yarn dev // 启动开发环境 打开浏览器输入http://localhost:3000/#/,如上图所示的话.那么恭喜你,你可以正常开发 React 项目了.完结撒花 如果不行的话,直接看 vite 官网,它比我写的详细 改造工

  • react获取input输入框的值的方法示例

    react 获取input 输入框的值的多种方式 第一种方式 非受控组件获取 第二种方式 受控组件获取 非受控组件获取 ref import React , {Component} from 'react'; export default class App extends Component{ search(){ const inpVal = this.input.value; console.log(inpVal); } render(){ return( <div> <input

  • React Router 如何使用history跳转的实现

    在react-router中组件里面的跳转可以用<Link> 但是在组件外面改如何跳转,需要用到react路由的history replace方法和push方法使用形式一样,replace的作用是取代当前历史记录 go,此方法用来前进或者倒退,history.go(-1); goBack,此方法用来回退,history.goBack(); goForward,此方法用来前进,history.goForward(); 1.hook import {useHistory} from 'react-

  • react实现Radio组件的示例代码

    本文旨在用最清楚的结构去实现一些组件的基本功能.希望和大家一起学习,共同进步 效果展示: 测试组件: class Test extends Component { constructor(props) { super(props) this.state = { active:1 } } onGroupChange(value) { this.setState({ active: value }) } render() { return ( <div> <RadioGroup onChan

  • 使用hooks写React组件需要注意的5个地方

    Hook是React16.8开始新增的特性.虽然React官方文档已经作出了针对React hooks的相关概念的讲解,但是光看官方文档是很难将hooks使用好的,在编写hooks的过程中很容易跳进陷阱和错误.本文总结了5个不好的地方. 01.不需要render的场景下使用useState 在函数组件中我们可以使用useState来管理状态,这使得对状态的管理变得很简单,但是也容易被滥用,我们通过下面的代码样例看下容易忽略的地方. 不推荐× function ClickButton(props)

  • react hooks入门详细教程

    State Hooks 案例: import { useState } from 'react'; function Example() { const [count, setCount] = useState(0); //count:声明的变量:setCount:改变count值的函数:0:count的初始值 return ( <div> <p>You clicked {count} times</p> <button onClick={() => set

  • 简单分析React中的EffectList

    React中,会遍历EffectList来执行节点操作.生命周期方法.Effect方法,可以把EffectList比作圣诞树上挂的彩灯,而这颗圣诞树就是Fiber树. 为什么会存在EffectList呢?打个比方来说,一颗Fiber树中有一些Fiber节点需要执行componentDidMount方法,如果在Fiber树构建完成后,再遍历一次Fiber树,找到需要执行componentDidMount方法的Fiber节点,这是非常低效的. 而EffectList就解决了这个问题,在Fiber树构

  • 简单谈谈React中的路由系统

    React中的路由系统 提起路由,首先想到的就是 ASPNET MVC 里面的路由系统--通过事先定义一组路由规则,程序运行时就能自动根据我们输入的URL来返回相对应的页面.前端中的路由与之类似,前端中的路由是根据你定义的路由规则来渲染不同的页面/组件,同时也会更新地址栏的URL.本篇文章要介绍的是React中经常使用到的路由,react-router主要使用HTML5的history API来同步你的UI和URL. react-router的最新版本是v4.1.1,由于4.0版本和之间的版本A

  • 简单分析javascript中的函数

    在脚本语言JavaScript中,函数的定义是由事件驱动或者当它被调用时可重复使用的代码块.在JavaScript的标准ECMAscript中,把函数表述为可以随时随地运行的语句.我个人是不认同ECMA的说法的,因为函数只有在发生调用的时候才会执行,否则就是一段毫无生气的代码. 我们来具体认识认识函数. (一)首先是函数的定义: 在ECMAscript函数的定义是 关键字function 函数名( 参数){主体:return(返回值)};这四部分组成的,但是在脚本语言中函数的定义却分为三种方式定

  • 简单分析MySQL中的primary key功能

    在5.1.46中优化器在对primary key的选择上做了一点改动: Performance: While looking for the shortest index for a covering index scan, the optimizer did not consider the full row length for a clustered primary key, as in InnoDB. Secondary covering indexes will now be pref

  • 简单分析js中的this的原理

    很多人可能会有疑问,this到底是什么?为什么this的值变化多端? 首先我们来概括下this. this是一个对象,一般存在于函数中,表示当前函数的执行上下文; 值得一提的是,当函数在执行后,this才有绑定的对象,函数未执行时,this没有内容 接下来我们看看在不同场景下的this的指向 1.函数默认执行 :此时this指向Window function fn(){ console.log(this) //Window }; fn(); 在严格模式开启后,this的指向又有所不同 严格模式下

  • 简单分析java中CMS回收器

    CMS(Concurrent Mark Sweep)回收器 它使用的是标记清除算法,同时又是一个使用多线程并行回收的垃圾回收器. CMS主要工作步骤 CMS工作时主要步骤有初始标记.并发标记.预清理.重新标记.并发清除和并发重置.其中初始标记和重新标记是独占系统资源的,而预清理.并发标记.并发清除和并发重置是可以和用户线程一起执行的.因此它可以在应用程序运行过程中进行垃圾回收. 根据标记清除算法,初始标记.并发标记和重新标记都是为了标记出需要回收的对象.并发清理则是在标记完成后,正是回收垃圾对象

  • react中的双向绑定你真的了解吗

    前言:因为项目原因需要学习另一个超级火的框架react, 因为之前一直使用vue进行开发,所以在学习react中会不自觉的代入一些vue中的概念来理解react中的实现,下面就通过对比学习的方式记录下react中的知识. 在react中其实是没有双向绑定的概念,每次更新页面和值都需要我们自己去调用指定的api来触发,而在vue中只需要使用v-model指令就可以完全实现,在vue中的指令其实也是使用了几个事件的语法糖来实现,所以下面就去看下react中的“双向绑定”是怎么来实现的. 如果实现re

  • 对python中的xlsxwriter库简单分析

    一.xlsxwriter 基本用法,创建 xlsx 文件并添加数据 官方文档:http://xlsxwriter.readthedocs.org/ xlsxwriter 可以操作 xls 格式文件 注意:xlsxwriter 只能创建新文件,不可以修改原有文件.如果创建新文件时与原有文件同名,则会覆盖原有文件 Linux 下安装: sudo pip install XlsxWriter Windows 下安装: pip install XlsxWriter # coding=utf-8 from

  • React中的Context应用场景分析

    Context定义和目的 Context 提供了一种在组件之间共享数据的方式,而不必显式地通过组件树的逐层传递 props. 应用场景 哪些数据会需要共享? Context 设计目的是为了共享那些对于一个组件树而言是**"全局"的数据**,例如当前认证的用户.主题或首选语言. 使用步骤 1. 创建并初始化Context const MyContext = createContex(defaultValue); 创建一个 Context 对象.当 React 渲染一个订阅了这个 Cont

  • Vue和React中快速使用Electron的简单教程

    目录 提示: 前言 一.前提 二.在Vue和React中快速使用 1. 安装Electron 2. 运行成功后,打包成软件 总结 提示: 本篇文章只是教你简单上手,Electron本身功能是非常强大的,这里边的结合,只是能让你简单实现一个桌面应用程序!如需要复杂的配置electron,建议去参考官网~ 前言 对于我们来说Electron相当于一个壳子,可以把写好的网页程序嵌入到壳子里面,可以运行在桌面上的一个程序,可以把网页打包成一个在桌面应用程序,也类似我们平时做的套壳打包的5+app,简单来

随机推荐