最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 浅析React中的EffectList

    正文概述 掘金(ZHANGYU)   2021-04-04   583

    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

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元