Skip to content

Latest commit

 

History

History
105 lines (76 loc) · 5.98 KB

File metadata and controls

105 lines (76 loc) · 5.98 KB

全栈潇晨 人人都能读懂的react源码解析

课程地址 https://xiaochen1024.com/courseware/60b1b2f6cf10a4003b634718/60b1b348cf10a4003b634720

render阶段整体执行流程

render阶段整体执行流程

  • 捕获阶段 从根节点rootFiber开始,遍历到叶子节点,每次遍历到的节点都会执行beginWork,并且传入当前Fiber节点,然后创建或复用它的子Fiber节点,并赋值给workInProgress.child。
  • 冒泡阶段 在捕获阶段遍历到子节点之后,会执行completeWork方法,执行完成之后会判断此节点的兄弟节点存不存在,如果存在就会为兄弟节点执行completeWork,当全部兄弟节点执行完之后,会向上‘冒泡’到父节点执行completeWork,直到rootFiber。最后形成一颗Fiber树,每个节点以child和return相连。

beginWork

beginWork主要的工作是创建或复用子fiber节点

function beginWork(
  current: Fiber | null,//当前存在于dom树中对应的Fiber树
  workInProgress: Fiber,//正在构建的Fiber树
  renderLanes: Lanes,//第12章在讲
): Fiber | null {
 // 1.update时满足条件即可复用current fiber进入bailoutOnAlreadyFinishedWork函数
 //2.根据tag来创建不同的fiber 最后进入reconcileChildren函数
}

首次渲染时除了rootFiber外,current 等于 null,因为首次渲染dom还没构建出来,在update时current不等于 null,因为update时dom树已经存在了,所以beginWork函数中用current === null来判断是mount还是update进入不同的逻辑

  • mount:根据fiber.tag进入不同fiber的创建函数,最后都会调用到reconcileChildren创建子Fiber
  • update:在构建workInProgress的时候,当满足条件时,会复用current Fiber来进行优化,也就是进入bailoutOnAlreadyFinishedWork的逻辑,能复用didReceiveUpdate变量是false,复用的条件是
    1. oldProps === newProps && workInProgress.type === current.type 属性和fiber的type不变
    2. !includesSomeLane(renderLanes, updateLanes) 更新的优先级是否足够

reconcileChildren

创建子fiber的过程会进入reconcileChildren,该函数的作用是为workInProgress fiber节点生成它的child fiber即 workInProgress.child。然后继续深度优先遍历它的子节点执行相同的操作。

区分mount和update两种情况,进入reconcileChildFibers或mountChildFibers

var reconcileChildFibers = ChildReconciler(true);
var mountChildFibers = ChildReconciler(false);

function ChildReconciler(shouldTrackSideEffects) {
	function placeChild(newFiber, lastPlacedIndex, newIndex) {
    newFiber.index = newIndex;
     
    if (!shouldTrackSideEffects) {//是否追踪副作用
      // Noop.
      return lastPlacedIndex;
    }
     
    var current = newFiber.alternate;
     
    if (current !== null) {
      var oldIndex = current.index;
     
      if (oldIndex < lastPlacedIndex) {
        // This is a move.
        newFiber.flags = Placement;
        return lastPlacedIndex;
      } else {
        // This item can stay in place.
        return oldIndex;
      }
    } else {
      // This is an insertion.
      newFiber.flags = Placement;
      return lastPlacedIndex;
    }
  }
}

reconcileChildren与effectTag与dom commit 在ChildReconciler中用shouldTrackSideEffects来判断是否为对应的节点打上effectTag 例如如果一个节点需要进行插入操作,需要满足两个条件:

  1. fiber.stateNode!==null 即fiber存在真实dom,真实dom保存在stateNode上
  2. (fiber.effectTag & Placement) !== 0 fiber存在Placement的effectTag

为Fiber打上effectTag之后在commit阶段会被执行对应dom的增删改,而且在reconcileChildren的时候,rootFiber是存在alternate的,即rootFiber存在对应的current Fiber,所以rootFiber会走reconcileChildFibers的逻辑,所以shouldTrackSideEffects等于true会追踪副作用,最后为rootFiber打上Placement的effectTag,然后将dom一次性插入,提高性能。

bailoutOnAlreadyFinishedWork

如果进入了bailoutOnAlreadyFinishedWork复用的逻辑,会判断优先级,优先级足够则进入cloneChildFibers否则返回null

completeWork

completeWork主要工作是处理fiber的props、创建dom、创建effectList

  • 根据workInProgress.tag进入不同函数,我们以HostComponent举例
  • update时(除了判断current===null外还需要判断workInProgress.stateNode===null),调用updateHostComponent处理props(包括onClick、style、children ...),并将处理好的props赋值给updatePayload,最后会保存在workInProgress.updateQueue上
  • mount时 调用createInstance创建dom,将后代dom节点插入刚创建的dom中,调用finalizeInitialChildren处理props(和updateHostComponent处理的逻辑类似)

在beginWork的mount时,rootFiber存在对应的current,所以他会执行mountChildFibers打上Placement的effectTag,在冒泡阶段也就是执行completeWork时,我们将子孙节点通过appendAllChildren挂载到新创建的dom节点上,最后就可以一次性将内存中的节点用dom原生方法反应到真实dom中。

在beginWork 中我们知道有的节点被打上了effectTag的标记,有的没有,而在commit阶段时要遍历所有包含effectTag的Fiber来执行对应的增删改,那我们还需要从Fiber树中找到这些带effectTag的节点嘛,答案是不需要的,这里是以空间换时间,在执行completeWork的时候遇到了带effectTag的节点,会将这个节点加入一个叫effectList中,所以在commit阶段只要遍历effectList就可以了(rootFiber.firstEffect.nextEffect就可以访问带effectTag的Fiber了)

effectList的指针操作发生在completeUnitOfWork函数中

形成环状链表的时候会从触发更新的节点向上合并effectList直到rootFiber,这一过程发生在completeUnitOfWork函数中,整个函数的作用就是向上合并effectList

然后commitRoot(root);进入commit阶段