• React18源码: Fiber树的初次创建过程图文详解


    fiber树构造(初次创建)

    • fiber树构造的2种情况:
      • 1.初次创建
        • 在React应用首次启动时,界面还没有渲染
        • 此时并不会进入对比过程,相当于直接构造一棵全新的树
      • 2.对比更新
        • React应用启动后,界面已经渲染
        • 如果再次发生更新,创建新fiber之前需要和旧fiber进行对比
        • 最后构造的fiber树有可能是全新的,也可能是部分更新的
    • 这里重点关注初次创建这种情况,主要突出fiber树构造过程
    • 下面会在 Legacy 模式下进行分析
    • 因为只讨论fiber树构造原理,Concurrent模式与Legacy没有区别

    示例代码

    class App extends React.Component {
      componentDidMount() {
        console.log('App Mount');
        console.log(`App组对应的fiber节点:`this._reactInternals);
      }
      render() {
        return (
          <div className="app">
            <header>header</header>
            <Content />
          </div>
        );
      }
    }
    
    class Content extends React.Component {
      componentDidMount() {
        console. log('Content Mount');
        console.log(`Content组应的fiber节:`this._reactInternals);
      }
      render() {
        return(
          <React. Fragment>
          <p>1</p>
          <p>2</p>
          </React. Fragment>
        );
      }
    }
    export default App;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    启动阶段

    • 在前文分析了2种启动模式的差异,在进入 react-reconciler 包之前(调用 updateContainer 之前)
    • 内存状态图如下:
    • 根据这个结构,可以在控制台中打出当前页面对应的fiber树(用于观察其结构):

      • document.getElementByld('root')._reactRootContainer._internalRoot.current;
    • 然后进入react-reconciler包调用updateContainer函数:

      // ... 省略了部分代码
      export function updateContainer(
        element: ReactNodeList,
        container: OpaqueRoot,
        parentComponent: ?ReactSComponent<any, any>,
        callback: ?Function,
      ): Lane {
        // 获取当前时间戳
        const current = container.current;
        const eventTime = requestEventTime();
        // 1.创建一个优先级变量(车遵模型)
        const lane = requestUpdateLane(current);
      
        // 2.根据车道优先级,创建update对象,并加入fiber.updateQueue.pending队列
        const update = createUpdate(eventTime, lane);
        update.payload = { element };
        callback = callback === undefined ? null : callback;
        if (callback !== null) {
          update.callback = callback;
        }
        enqueueUpdate(current, update);
        // 3. 进入reconciler运作流程中的`输入环节
        scheduleUpdateOnFiber(current, lane, eventTime);
        return lane;
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
    • 由于 update 对象的创建,此时的内存结构如下

    • 注意
      • 最初的ReactElement对象被挂载到
      • HostRootFiber.updateQueue.shared.pending.payload.element 中,
      • 后面fiber树构造过程中会再次变动

    构造阶段

    • 为了突出构造过程,排除干扰,先把内存状态图中的 FiberRoot 和 HostRootFiber 单独提出来
    • 在 scheduleUpdateOnFiber 函数中

      //...省略部分代码
      export function scheduleUpdateOnFiber(
        fiber: Fiber,
        lane:Lane,
        eventTime: number,
      ) {
        // 标记优先级
        const root = markUpdateLaneFromFiberToRoot(fiber, lane);
        if(lane === SyncLane) {
          if(
            (executionContext & LegacyUnbatchedContext) !== NoContext &&
            (executionContext & (RenderContext CommitContext)) === NoContext
          ) {
            // 首次渲染,直接进行fiber构造
            performSyncWorkOnRoot(root);
          }
          // ...
        }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
    • 可以看到,在Legacy模式下且首次渲染时

    • 有2个函数 markUpdateLaneFromFiberToRoot 和 performSyncWorkOnRoot

    • 其中 markUpdateLaneFromFiberToRoot(fiber,lane)函数在fiber树构造(对比更新)中才会发挥作用

    • 因为在初次创建时并没有与当前页面所对应的fiber树,所以核心代码并没有执行,最后直接返回了FiberRoot对象

    • performSyncWorkOnRoot看起来源码很多,初次创建中真正用到的就2个函数:

      function performSyncWorkOnRoot(root) {
        let lanes;
        let exitStatus;
        if (
          root === workInProgressRoot &&
          includesSomeLane(root.expiredLanes, workInProgressRootRenderLanes)
        ) {
          // 初次构造时(因为root = fiberRoot,workInProgressRoot=null),所以不会进入
        } else {
          // 1. 获取本次render的优先级,初次构造返回 NoLanes
          lanes = getNextLanes(root, NoLanes);
          // 2. 从root节点开始,至上而下更新
          exitStatus = renderRootSync(root, lanes);
        }
      
        // 将最新的fiber树挂载到root.finishedWork节点上
        const finishedWork: Fiber = (root.current.alternate: any);
        root.finishedWork = finishedWork;
        root.finishedlanes = lanes;
        // 进入commit阶段
        commitRoot(root);
        //...后面的内容跳过
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
    • 其中 getNextLanes 返回本次 render 的渲染优先级中

    • renderRootSync

      function renderRootSync(root: FiberRoot, lanes: Lanes) {
        const prevExecutionContext = executionContext;
        executionContext |= RenderContext;
      
        // 如果fiberRoot变动,或者update.Lone变动,都会刷新栈帧,丢弃上一次渲染进度
        if (workInProgressRoot !== root || workInProgressRootRenderLanes !== lanes) {
          // 刷新栈帧,legacy模式下都会进入
          prepareFreshStack(root,lanes);
        }
        do {
          try {
            workLoopSync();
            break;
          } catch (thrownValue) {
            handleError(root, thrownValue);
          }
        } while (true);
        executionContext = prevExecutionContext;
        // 重置全局变量,表明render束
        workInProgressRoot = null;
        workInProgressRootRenderLanes = NoLanes;
        return workInProgressRootExitStatus;
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
    • 在 renderRootSync 中,在执行fiber树构造前(workLoopSync)会先刷新栈帧

    • prepareFreshStack 在这里创建了 HostRootFiber.alternate

    • 重置局变量 workInProgress 和 workInProgressRoot 等

    循环构造

    • 逻辑来到 workLoopSync, 绥安本节在 Legacy 模式下进行讨论

    • 此处还是对比一下 workLoopConcurrent

      function workLoopSync() {
        while (workInProgress !== null) {
          performUnitOfWork(workInProgress);
        }
      }
      
      function workLoopConcurrent() {
        // Perform work until Scheduler asks us to yield
        while (workInProgress !== null && !shouldYield()) {
          performUnitOfwork(workInProgress);
        }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
    • 可以看到workLoopConcurrent相比于Sync,会多一个停顿机制

    • 这个机制实现了时间切片和可中断染

    • 结合 performUnitOfWork 函数

      // ...省略部分无关代码
      function performUnitOfWork(unitofwork: Fiber): void {
        // unitOfWork 就是被传入的 workInProgress
        const current = unitOfWork.alternate;
        let next;
        next = beginWork(current, unitOfWork, subtreeRenderLanes);
        unitOfWork.memoizedProps = unitOfWork.pendingProps;
        if (next === null) {
          // 如果没有派生出新的节点,则进入completeWork阶段,传入的是当前unitOfWork
          completeUnitOfWork(unitOfWork);
        } else {
          workInProgress = next;
        }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
    • 可以明显的看出,整个fiber树构造是一个深度优先遍历其中有2个重要的变量workInProgress和current(双缓冲技术)

      • workInProgress 和 current 都视为指针
      • workInProgress 指向当前正在构造的fiber节点
      • current = workInProgress.alternate(即fiber.alternate), 指向当前页面正在使用的fiber节点.
      • 初次构造时,页面还未渲染,此时current = null
    • 在深度优先遍历中,每个 fiber 节点都会经历2个阶段

      • 1.探寻阶段 beginWork
      • 2.回溯阶段 completeWork
    • 这2个阶段共同完成了每一个fiber节点的创建,所有fiber节点则构成了fiber树.

    探寻阶段 beginWork

    • beginWork(current, unitOfWork, subtreeRenderLanes)

    • 针对所有的 Fiber 类型,其中的每一个 case 处理一种 Fiber 类型.

    • updateXXX函数(如:updateHostRoot, updateClassComponent等)的主要逻辑:

      • 1.根据 ReactElement对象创建所有的fiber节点,最终构造出fiber树形结构(设置 return和sibling指针)

      • 2.设置 fiber.flags (二进制形式变量,用来标记 fiber节点的增,删,改状态,等待completeWork阶段处理)

      • 3.设置 fiber.stateNode 局部状态(如Class类型节点:fiber.stateNode=new Class())

        function beginwork(
          current: Fiber | null,
          workInProgress: Fiber,
          renderLanes: Lanes,
        ): Fiber | null {
          const updateLanes = workInProgress.lanes;
          if (current !== null) {
            // update逻辑,首次render不会进入
          } else {
            didReceiveUpdate = false;
          }
          // 1.设置workInProgress优先级为NoLanes(最高优先级)
          workInProgress.lanes = NoLanes;
          // 2.根据workInProgress节点的类型,用不同的方法派生出子节点
          switch (
            workInProgress.tag //了本例使用到case
          ) {
            case ClassComponent: {
              const Component = workInProgress.type;
              const unresolvedProps = workInProgress. pendingProps;
              const resolvedProps =
                workInProgress.elementType === Component
                ? unresolvedProps
                : resolveDefaultProps(Component, unresolvedProps);
              return updateclassComponent(
                    current,
                    workInProgress,
                    Component,
                    resolvedProps,
                    renderLanes,
                  );
            }
            case HostRoot:
              return updateHostRoot(current, workInProgress, renderLanes);
            case HostComponent:
              return updateHostComponent(current, workInProgress, renderLanes);
            case HostText:
              return updateHostText(current, workInProgress);
            case Fragment:
              return updateFragment(current, workInProgress, renderLanes);
          }
        }
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
        • 9
        • 10
        • 11
        • 12
        • 13
        • 14
        • 15
        • 16
        • 17
        • 18
        • 19
        • 20
        • 21
        • 22
        • 23
        • 24
        • 25
        • 26
        • 27
        • 28
        • 29
        • 30
        • 31
        • 32
        • 33
        • 34
        • 35
        • 36
        • 37
        • 38
        • 39
        • 40
        • 41
        • 42
    • updateXXX函数(如: updateHostRoot, updateClassComponent等)虽然case较多

    • 但是主要逻辑可以概括为3个步骤

      • 1.根据fiber.pendingProps, fiber.updateQueue等输数据状态
        • 计算fiber.memoizedState作为输出状态
      • 2.获取下级ReactElement对
        • a. class类型的fiber节点
          • 构建 React.Component 实例
          • 把新实例挂载到 fiber.stateNode 上
          • 执行render之前的生命周期函数
          • 执行render方法,获取下级 reactElement
          • 根据实际情况,设置fiber.flags
        • b.function 类型的 fiber 节点
          • 执行function, 获取下级reactElement
          • 根据实际情况,设置fiber.flags
        • c. HostComponent类型(如: div, span, button等)的 fiber节点
          • pendingProps.children作为下级 reactElement
          • 如果下级节点是文本节点,则设置下级节点为null. 准备进入completeUnitOfWork阶段
          • 根据实际情况设置fiber.flags
        • d.其他类型
      • 3.根据ReactElement对象,调用reconcileChildren生成Fiber子节点(只生成次级子节点)
        • 根据实际情况,设置fiber.flags
    • 不同的updateXXX函数处理的fiber节点类型不同总的目的是为了向下生成子节点

    • 在这个过程中把一些需要持久化的数据挂载到fiber节点上

    • 如fiber.stateNode,fiber.memoizedState等把fiber节点的特殊操作设置到fiber.flags

    • 如:节点ref,class组件的生命周期,function组件的hook,节点删除等

    • 这里列出updateHostRoot,updateHostComponent的代码,对于其他常用case的分析

    • 如class类型,function类型

    • fiber树的根节点是 HostRootFiber 节点

    • 所以第一次进入beginWork会调用updateHostRoot(current, worklnProgress, renderLanes)

      // 省略无关代码
      function updateHostRoot(current, workInProgress, renderlanes) {
        // 1、状态计算,更新整合到workInProgress.memoizedState中来
        const updateQueue = workInProgress.updateQueue;
        const nextProps = workInProgress.pendingProps;
        const prevState = workInProgress.memoizedState;
        const prevChildren = prevState !== null ? prevState.element : null;
        cloneUpdateQueue(current, workInProgress);
        //遍历updateQueue.shared.pending,提取有足够优先级的update对象,计算出最终的状态 workInProgres.
        processUpdateQueue(workInProgress, nextProps, null, renderLanes);
        const nextState = workInProgress.memoizedState;
        // 2.获取下级'ReactElement"对象
        const nextChildren = nextState.element;
        const root: FiberRoot = workInProgress.stateNode;
        if (root.hydrate && enterHydrationState(workInProgress)) {
          //..服务端渲染相关,此处省路
        } else {
          // 3.根据'ReactElement"对象,调用 reconcileChildren'生成"Fiber"子节点(只生成"次级子节点")
          reconcileChildren(current, workInProgress, nextChildren, renderLanes);
        }
        return workInProgress.child;
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
    • 普通DOM标签类型的节点(如div,span,p), 会进入 updateHostComponent:

      // ...省略部分无关代码
      function updateHostComponent(
        current: Fiber| null,
        workInProgres: Fiber,
        renderLanes: Lanes,
      ) {
        // 1. 状态计算,由于HostComponent是无状态组件,所以只需要收集 nextProps即可,它没有 memoizedState
        const type = workInProgress.type;
        const nextProps = workInProgress.pendingProps;
        const prevProps = current !== null ? current.memoizedProps : null;
        // 2. 获取下级ReactElement对象
        let nextChildren = nextProps.children;
        const isDirectTextChild = shouldSetTextContent(type, nextProps);
        if(isDirectTextChild) {
          // 如果子节点只有一个文本节点,不用再创建一个HostText类型的fiber
          nextChildren = null;
        } else if (prevProps != null && shouldSetTextContent(type, prevProps)) {
          // 特殊操作需要设置fiber.flags
          workInProgress.flags |= ContentReset;
        }
        // 特殊操作需要设置fiber.flags
        markRef(current, workInProgress);
        // 3. 根据`ReactElement'对象,调用`reconcileChildren'生成Fiber`子节点(只生成`次级子节点`)
        reconcileChildren(current, workInProgress, nextChildren, renderLanes);
        return workInProgress.child;
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26

    回溯阶段 completeWork

    • completeUnitOfWork(unitOfWork) 处理 beginWork 阶段已经创建出来的 fiber 节点

    • 核心逻辑

      • 1.调用completeWork

        • 给fiber节点(tag=HostComponent, HostText)创建DOM实例(内存中)
        • 设置 fiber.stateNode局部状态(如 tag=HostComponent, HostText节点: fiber.stateNode指向这个DOM实例).
        • 为DOM节点设置属性,绑定事件(涉及合成事件)
        • 设置fiber.flags标记
      • 2.把当前fiber对象的副作用队列(firstEffect 和 lastEffect)添加到父节点的副作用队列之后

        • 更新父节点的firstEffect和lastEffect指针
      • 3.识别beginWork阶段设置的fiber.flags

        • 判断当前fiber是否有副作用(增,删,改)
        • 如果有,需要将当前fiber加入到节点的 effects 队列,等commit阶段处理
        function completeUnitOfWork(unitOfWork: Fiber): void {
          let completedWork = unitOfWork;
          // 外层循环控制并移动指针(`workInProgress',`completedWork"等)
          do {
            const current = completedWork.alternate;
            const returnFiber = completedWork.return;
            if ((completedWork.flags & Incomplete) === NoFlags) {
              let next;
              // 1.处理Fiber节点,会调用渲染器(调用react-dom包,关联Fiber节点和dom对象,绑定事件等)
              next = completeWork(current, completedWork, subtreeRenderLanes); //处理单个节点
              if (next !== null) {
                // 如果派生出其他的子节点,则回到`beginWork"阶段进行处理
                workInProgress = next;
                return;
              }
              // 重置子节点的优先级
              resetChildLanes(completedWork);
              if (
                returnFiber !== null &&
                (returnFiber.flags & Incomplete) === NoFlags
              ) {
                // 2.收集当前Fiber节点以及其子树的副作用effects
                // 2.1把子节点的副作用队列添加到父节点上
                if (returnFiber.firstEffect == null) {
                  returnFiber.firstEffect = completedWork.firstEffect;
                }
                if (completedWork.lastEffect !== null) {
                  if (returnFiber.lastEffect !== null) {
                    returnFiber.lastEffect.nextEffect = completedWork.firstEffect;
                  }
                  returnFiber.lastEffect = completedWork.lastEffect;
                }
                // 2.2如果当前fiber节点有副作用,将其添加到子节点的副作用队列之后.
                const flags = completeWork.flags;
                if (flags > PerformedWork) {
                  // PerformedWork是提供给 React DevTools读取的,所以略过PerformedWork
                  if (returnFiber.lastEffect !== null){
                    returnFiber.lastEffect.nextEffect = completedWork;
                  } else {
                    returnFiber.firstEffect = completedWork;
                  }
                  returnFiber.lastEffect= completedWork;
                }
              } else {
                // 异常处理,本节不讨论
              }
              
              const siblingFiber = completedWork.sibling;
              if (siblingFiber !== null) {
                // 如果有兄弟节点,返回之后再次进入`beginWork`阶段
                workInProgress = siblingFiber;
                return;
              }
              // 移动指针,指向下一个节点
              completedWork = returnFiber;
              workInProgress = completedWork;
          } while (completedWork !== null);
          // 已回溯到根节点,设置workInProgressRootExitStatus = RootCompleted
          if (workInProgressRootExitStatus === RootIncomplete) {
            workInProgressRootExitStatus = RootCompleted;
          }
        }
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
        • 9
        • 10
        • 11
        • 12
        • 13
        • 14
        • 15
        • 16
        • 17
        • 18
        • 19
        • 20
        • 21
        • 22
        • 23
        • 24
        • 25
        • 26
        • 27
        • 28
        • 29
        • 30
        • 31
        • 32
        • 33
        • 34
        • 35
        • 36
        • 37
        • 38
        • 39
        • 40
        • 41
        • 42
        • 43
        • 44
        • 45
        • 46
        • 47
        • 48
        • 49
        • 50
        • 51
        • 52
        • 53
        • 54
        • 55
        • 56
        • 57
        • 58
        • 59
        • 60
        • 61
        • 62
    • fiber 处理函数 completeWork

      function completeWork(
        current: Fiber | null,
        workInProgress: Fiber,
        renderLanes: Lanes,
      ): Fiber | null {
        const newProps = workInProgress.pendingProps;
        switch(workInProgress.tag) {
          case ClassComponent: {
            // Class类型不做处理
            return null;
          }
          case HotRoot: {
            const fiberRoot = (workInProgress.stateNode: FiberRoot);
            if (fiberRoot.pendingContext) {
              fiberRoot.context = fiberRoot.pendingContext;
              fiberRoot.pendingContext= null;
            }
            if (current === null || current.child == null){
              // 设置fiber.flags记
              workInProgress.flags = Snapshot;
            }
            return null;
          }
          case HostComponent: {
            popHostContext(workInProgress);
            const rootContainerInstance = getRootHostContainer();
            const type = workInProgress.type;
            if (current !== null && workInProgress.stateNode !== null) {
              // update逻辑,初次render不会进入
            } else {
              const currentHostContext = getHostContext();
              // 1.创建DOM对象
              const instance = createInstance(
                type,
                newProps,
                rootContainerInstance,
                currentHostContext,
                workInProgress,
              );
              // 2、把子树中的DOM对象append到本节点的DOM对象之后
              appendAllChildren(instance, workInProgress, false, false);
              // 设置stateNode届性,指向DOM对象
              workInProgress.stateNode = instance;
              if(
                // 3,设置DOM对象的属性,绑定事件等
                finalizeInitialChildren(
                  instance,
                  type,
                  newProps,
                  rootContainerInstance,
                  currentHostContext,
                )
              ) {
                // 设置fiber. flags标(Update)
                markUpdate(workInProgress);
              }
              if (workInProgress.ref !== null) {
                //设置fiber.flags标(Ref)
                markRef(workInProgress);
              }
              return null;
            }
          }
        }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
      • 36
      • 37
      • 38
      • 39
      • 40
      • 41
      • 42
      • 43
      • 44
      • 45
      • 46
      • 47
      • 48
      • 49
      • 50
      • 51
      • 52
      • 53
      • 54
      • 55
      • 56
      • 57
      • 58
      • 59
      • 60
      • 61
      • 62
      • 63
      • 64
      • 65
    • 可以看到在满足条件的时候也会设置 fiber.flags, 所以设置 fiber.flags 并非只在 beginWork 阶段

    过程图解

    • 基于一个小例子来概述
      class App extends React.Component {
        componentDidMount() {
          console.log('App Mount');
          console.log(`App组对应的fiber节点:`this._reactInternals);
        }
      
        render() {
          return(
            <div className="app">
              <header>header</header>
              <Content />
            </div>
          );
        }
      }
      
      class Content extends React.Component {
        componentDidMount() {
          console. log('Content Mount');
          console.log(`Content组应的fiber节点:`this._reactInternals);
        }
      
        render() {
          return(
            <React.Fragment>
              <p>1</p>
              <p>2</p>
            </React.Fragment>
          );
        }
      }
      
      export default App;
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
    • 针对本节的示例代码,将整个fiber树构造过程表示出来:
    • 将整个fiber树构造过程用图表示出来
    • 构造前:
      • 进入循环构造前会调用prepareFreshstack刷新栈帧
      • 在进入fiber树构造循环之前,保持这个初始化状态
    • performUnitOfWork 第1次调用(只执行 beginWork):
      • 执行前
        • workInProgress 指针指向 HostRootFiber.alternate 对象
        • 此时 current = workInProgress.alternate 指向 fiberRoot.current 是非空的
        • 初次构造,只在根节点时,current 非空
      • 执行过程
        • 调用 updateHostRoot
        • 在 reconcileChildren 阶段
        • 向下构造次级子节点 fiber(), 同时设置子节点 (fiber())
        • fiber.flags |= Placement
      • 执行后
        • 返回下级节点 fiber()
        • 移动 workInProgress 指针指向子节点 fiber()
    • performUnitOfWork第2调用 (beginWork):
      • 执行前:
        • workInProgress 指针指向 fiber()节点,此时 current = null
      • 执行过程:
        • 调用 updateClassComponent
        • 本示例中,class实例存在生命周期函数componentDidMount,
        • 所以会设置fiber()节点, workInProgress.flags |= Update
        • 另外也会为了 React DevTools 能够识别状态组件的执行进度,会设置
        • workInProgress.flags |= PerformedWork
        • 在commit阶段会排除这个 flag, 此处只是列出 workInProgress.flags 的设置场景, 不讨论 React DevTools
        • 需要注意 classInstance.render()在本步骤执行后,虽然返回了 render 方法中所有的ReactElement对象
        • 但是随后 reconcileChildren 只构造次级子节点
        • 在 reconcileChildren 阶段,向下构造次级子节点div
      • 执行后:
        • 返回下级节点fiber(div)
        • 移动 workInProgress 指针指向子节点fiber(div)
    • performUnitofwork第3次调用(只执行 beginWork):
      • 执行前:
        • workInProgress指针指向fiber(div)节点,此时 current=null
      • 执行过程:
        • 调用updateHostComponent
        • 在reconcileChildren阶段,向下构造次级子节点(本示例中,div有2个次级子节点)
      • 执行后:
        • 返回下级节点fiber(header), 移动 workInProgress 指针指向子节点fiber(header)
    • performUnitOfwork第4次调用(行 beginWork和 completeUnitOfWork):
      • beginWork执行前:
        • workInProgress指针指向 fiber(header)节点,此时 current = null
      • beginWork行过程:
        • 调用updateHostComponent
        • 本示例中header的子节点是一个直接文本节点,设置nextChildren=null
        • 直接文本节点并不会被当成具体的fiber节点进行处理
        • 而是在宿主环境(父组件)中通过属性进行设置
        • 所以无需创建HostText类型的fiber节点,同时节省了向下遍历开销
        • 由于 nextChildren = null, 经过 reconcileChildren 阶段处理后,返回值也是null
      • beginWork执行后:
        • 由于下级节点为null, 所以进入completeUnitOfWork(unitOfWork)函数
        • 传入的参数unitOfWork实际上就是 workInProgress(此时指向 fiber(header)节点)
    • completeUnitOfWork 执行前:

      • workInProgress 指针指向 fiber(header)节点
    • completeUnitOfWork 执行过程:

      • 以fiber(header)为起点,向上回溯
    • 第1次循环

      • 1.执行 completeWork 函数
        • 创建fiber(header)节点对应的DOM实例,并append子节点的DOM实例(在内存中)
        • 设置DOM属性,绑定事件等(本示例中,节点fiber(header)没有事件绑定)
      • 2.上移副作用队列
        • 由于本节点fiber(header)没有副作用(fiber.flags=0)
        • 所以执行之后副作用队列没有实质变化(目前为空)
      • 3.向上回溯
        • 由于还有兄弟节点,把workInProgress指针指向下一个兄弟节点fiber()
        • 退出 completeUnitOfWork
    • performUnitOfWork第5次(beginWork):
      • 执行前:workInProgress 指针指向 fiber()节点
      • 执行过程:这是一个class类型的节点,与第2次调用逻辑一致
      • 执行后:返回下级节点fiber(p),移动workInProgress指针指向子节点fiber(p)
    • performUnitOfWork 第6次调用(执行 beginWork和 completeUnitOfwork)
    • 与第4次调用中创建fiber(header)节点的逻辑一致.先后会执行beginWork和completeUnitOfWork
    • 最后构造DOM实例,并将把workInProgress指针指向下一个兄弟节点fiber§
    • performUnitOfwork第7次调用(执行 beginWork 和 completeUnitOfWork)
    • beginwork执行过程
      • 与上次调用中创建fiber§节点的逻辑一致
    • completeUnitOfWork执行过程
      • 以fiber§为起点,向上回溯
    • 第1次循环
      • 1.执行completeWork函数:
        • 创建fiber§节点对应的DOM实例,并append子树节点的DOM实例
      • 2.上移副作用队列:
        • 由于本节点fiber§没有副作用,所以执行之后副作用队列没有实质变化(目前为空)
      • 3.向上回溯:
        • 由于没有兄弟节点,把workInProgress指针指向父节点fiber()
    • 第2次循环:
      • 1.执行completeWork函数:
        • class类型的节点不做处理
      • 2.上移副作用队列:
        • 本节点fiber()的flags标志位有改动( completedWork.flags > PerformedWork)
        • 将本节点添加到父节点(fiber(div))的副作用队列之后
        • firstEffect和lastEffect属性分别指向副作用队列的首部和尾部
      • 3.向上回溯:
        • 把workInProgress指针指向节点fiber(div)
    • 第3次循环:
      • 1.执行completeWork函数:
        • 创建fiber(div)节点对应的DOM实例,并append子树节点的DOM实例
      • 2.上移副作用队列:
        • 本节点fiber(div)的副作用队列不为空,将其拼接到父节点fiber的副作用队列后面
      • 3.向上回溯:
        • 把workInProgress指针指向父节点fiber()
    • 第4次循环:
      • 1.执行completework函数:class类型的节点不做处理
      • 2.上移副作用队列:
        • 本节点fiber()的副作用队列不为空
        • 将其拼接到父节点fiber(HostRootFiber)的副作用队列上
        • 本节点fiber()的flags标志位有改动(completedWork.flags > Performedwork)
        • 将本节点添加到父节点fiber(HostRootFiber)的副作用队列之后
        • 最后队列的顺序(Effect顺序)是子节点在前,本节点在后
      • 3.向上回溯:
        • 把workInProgress指针指向父节点fiber(HostRootFiber)
    • 第5次循环:
      • 1.执行completeWork函数:
        • 对于 HostRoot类型的节点,初次构造时设置
        • workinProgress.flags |= Snapshot
      • 2.向上回溯:
        • 由于父节点为空,无需进入处理副作用队列的逻辑
        • 最后设置 workInProgress = null
        • 并退出 completeUnitOfWork
    • 到此整个fiber树构造循环已经执行完毕,拥有一棵完整的fiber树
    • 并且在fiber树的根节点上挂载了副作用队列,副作用队列的顺序是层级越深子节点越靠前
    • renderRootsync函数退出之前,会重置 workInProgressRoot = null
    • 表明没有正在进行中的 render,且把最新的fiber树挂载到 fiberRoot.finishedwork
    • 这时整个fiber树的内存结构如下
    • 注意fiberRoot.finishedwork和fiberRoot.current指针,在commitRoot阶段会进行处理

    总结

    • 首先我们有一个探寻的过程,首先探寻到App,然后从App到div,再到 header,发现没有子元元素了
    • 所以到 Content,到Content之前会做一件事情,就是把它自己需要的dom,在内存中创建出来
    • 包括包含的哪些属性也创建出来,生成一个effect的一个属性,合并到div里面去
    • 这个就是说白了,探寻到header, header进行回溯,我在内存中创建了一些东西
    • 因为这个在更新的时候要用,总得找个地方存起来,存起来的东西就叫做 effect
    • 存在哪里呢?存在它的父级,即它的return属性,就是它的父级 div 这里
    • header没有子元素,但是有兄弟节点,接下来回溯到 Content
    • 又开始继续往下去探寻,看到了 p,这个时候 p 没有子元素了
    • 这个时候p在回溯之前,也会跟 header 一样, 把跟自己相关的 dom 元素创建出来
    • 创建完之后,打一个 flag,然后搞一个 effect,因为它得把它自己要怎么更新
    • 以及更新哪些内容给它记录下来,就是通过这个effect
    • effect 是一个链表, 这个就是在后面 commit 阶段真正渲染的时候要用的
    • 之后,这个p标签就把它的effect也给合并到Content的effect的链表里面
    • 接下来p完成之后开始回溯, 发现它有兄弟结点也是个p,这个p跟前面的一样操作
    • 最后这个p把自己的一些东西生成在内存中, 生成之后搞一个 effect
    • 然后合并到 Content,也就是它的父元素里面
    • 最后的这个p开始继续回溯,回到了Content,Content 自己也要回溯
    • Content 自己只是需要做一些事情,就是给自己打一些标记,打一些flag
    • 因为 Content 自己也有一些 effect,之前两个p标签的effect都合并到了 Content 里
    • 这个时候 Content 会把它的effect合并到div里,之后回溯到div,也同样重复这类操作
    • 之后回溯到App, 继续重复此类操作:打flag, 生成dom,将自己effect合并到上级effect链表
    • 最后从App回溯到HostRootFiber, 此时我们的effect 链表已经特别庞大了
    • 这个effect的链表包含我们整棵表树更新的信息,包含怎么更新,更新的内容是什么,DOM元素是哪些等
    • 所以最后Fiber树创建完成之后,就带着这个effect链表
    • 接下来就进行我们页面的真正的渲染环节,总体来说,和React16版本的流程,区别不大
  • 相关阅读:
    vben admin配置详解(Table, Form)
    WebSocket 协议详解
    SpringCache
    AI加速器与机器学习算法:协同设计与进化
    hive库表常用命令及常用函数
    分组交换技术
    Ubuntu 22.04 安装Nvidia显卡驱动、CUDA、cudnn
    系统篇: linux 下增加交换分区
    2022年全球市场弦乐器的弦总体规模、主要生产商、主要地区、产品和应用细分研究报告
    (十三)强缓存和协商缓存的区别
  • 原文地址:https://blog.csdn.net/Tyro_java/article/details/136281060