简单分析React中的EffectList

 更新时间:2021年04月07日 10:15:28   作者:zhangyu  
这篇文章主要简单分析了React中的EffectList,帮助大家更好的理解和学习使用React进行前端开发,感兴趣的朋友可以了解下

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的资料请关注脚本之家其它相关文章!

相关文章

  • React 父子组件通信的实现方法

    React 父子组件通信的实现方法

    这篇文章主要介绍了React 父子组件通信的实现方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-12-12
  • 详解如何在React函数式组件中使用MobX

    详解如何在React函数式组件中使用MobX

    MobX 是一个简洁的状态管理库,它通过透明的函数响应式编程(TFRP)使得状态管理变得简单和可扩展,下面就跟随小编一起来了解一下如何在React函数式组件中使用MobX吧
    2024-01-01
  • React Native自定义组件与输出方法详解

    React Native自定义组件与输出方法详解

    这篇文章主要给大家介绍了关于React Native自定义组件与输出方法的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2018-07-07
  • react中通过props实现父子组件间通信的使用示例

    react中通过props实现父子组件间通信的使用示例

    在React中,父组件可以通过props属性向子组件传递数据,子组件可以通过props属性接收父组件传递过来的数据,本文就来介绍一下如何实现,感兴趣的可以了解一下
    2023-10-10
  • 使用react render props实现倒计时的示例代码

    使用react render props实现倒计时的示例代码

    这篇文章主要介绍了使用react render props实现倒计时的示例代码,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-12-12
  • react中context传值和生命周期详解

    react中context传值和生命周期详解

    这篇文章主要介绍了react中context传值和生命周期,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2023-03-03
  • React中useState原理的代码简单实现

    React中useState原理的代码简单实现

    要实现useState的背后原理,则需要深入了解状态是如何在函数组件的渲染周期中保持和更新的,本文将通过一段代码简单阐述useState钩子函数的实现思路,希望对大家有所帮助
    2023-12-12
  • React中代码分割的4种实现方式

    React中代码分割的4种实现方式

    虽然一直有做react相关的优化,按需加载、dll 分离、服务端渲染,但是从来没有从路由代码分割这一块入手过,所以下面这篇文章主要给大家介绍了关于React中代码分割的4种实现方式,需要的朋友可以参考下
    2022-01-01
  • React 中的 useContext使用方法

    React 中的 useContext使用方法

    这篇文章主要介绍了React中的useContext使用,使用useContext在改变一个数据时,是通过自己逐级查找对比改变的数据然后渲染,本文通过示例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2022-08-08
  • React中useEffect函数的使用详解

    React中useEffect函数的使用详解

    useEffect是React中的一个钩子函数,用于处理副作用操作,这篇文章主要为大家介绍了React中useEffect函数的具体用法,希望对大家有所帮助
    2023-08-08

最新评论