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

news2025/1/17 3:51:37

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;

启动阶段

  • 在前文分析了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;
    }
    
  • 由于 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);
        }
        // ...
      }
    }
    
  • 可以看到,在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);
      //...后面的内容跳过
    }
    
  • 其中 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;
    }
    
  • 在 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);
      }
    }
    
  • 可以看到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;
      }
    }
    
  • 可以明显的看出,整个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);
        }
      }
      
  • 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;
    }
    
  • 普通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;
    }
    

回溯阶段 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;
        }
      }
      
  • 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;
          }
        }
      }
    }
    
  • 可以看到在满足条件的时候也会设置 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;
    
  • 针对本节的示例代码,将整个fiber树构造过程表示出来:
  • 将整个fiber树构造过程用图表示出来
  • 构造前:
    • 进入循环构造前会调用prepareFreshstack刷新栈帧
    • 在进入fiber树构造循环之前,保持这个初始化状态
  • performUnitOfWork 第1次调用(只执行 beginWork):
    • 执行前
      • workInProgress 指针指向 HostRootFiber.alternate 对象
      • 此时 current = workInProgress.alternate 指向 fiberRoot.current 是非空的
      • 初次构造,只在根节点时,current 非空
    • 执行过程
      • 调用 updateHostRoot
      • 在 reconcileChildren 阶段
      • 向下构造次级子节点 fiber(<App/>), 同时设置子节点 (fiber(<App/>))
      • fiber.flags |= Placement
    • 执行后
      • 返回下级节点 fiber(<App/>)
      • 移动 workInProgress 指针指向子节点 fiber(<App/>)
  • performUnitOfWork第2调用 (beginWork):
    • 执行前:
      • workInProgress 指针指向 fiber(<App/>)节点,此时 current = null
    • 执行过程:
      • 调用 updateClassComponent
      • 本示例中,class实例存在生命周期函数componentDidMount,
      • 所以会设置fiber(<App/>)节点, 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(<Content/>)
      • 退出 completeUnitOfWork
  • performUnitOfWork第5次(beginWork):
    • 执行前:workInProgress 指针指向 fiber(<Content/>)节点
    • 执行过程:这是一个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(<Content/>)
  • 第2次循环:
    • 1.执行completeWork函数:
      • class类型的节点不做处理
    • 2.上移副作用队列:
      • 本节点fiber(<Content/>)的flags标志位有改动( completedWork.flags > PerformedWork)
      • 将本节点添加到父节点(fiber(div))的副作用队列之后
      • firstEffect和lastEffect属性分别指向副作用队列的首部和尾部
    • 3.向上回溯:
      • 把workInProgress指针指向节点fiber(div)
  • 第3次循环:
    • 1.执行completeWork函数:
      • 创建fiber(div)节点对应的DOM实例,并append子树节点的DOM实例
    • 2.上移副作用队列:
      • 本节点fiber(div)的副作用队列不为空,将其拼接到父节点fiber<App/>的副作用队列后面
    • 3.向上回溯:
      • 把workInProgress指针指向父节点fiber(<App/>)
  • 第4次循环:
    • 1.执行completework函数:class类型的节点不做处理
    • 2.上移副作用队列:
      • 本节点fiber(<App/>)的副作用队列不为空
      • 将其拼接到父节点fiber(HostRootFiber)的副作用队列上
      • 本节点fiber(<App/>)的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版本的流程,区别不大

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1470419.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Java 过滤器深入了解学习

Java 过滤器深入了解学习 生活不能等待别人来安排&#xff0c;要自己去争取和奋斗&#xff1b;而不论其结果是喜是悲&#xff0c;但可以慰藉的是&#xff0c;你总不枉在这世界上活了一场。有了这样的认识&#xff0c;你就会珍重生活&#xff0c;而不会玩世不恭&#xff1b;同时…

【服务发现--service】

1、service的定义 "Service"简写"svc”。Pod不能直接提供给外网访问&#xff0c;而是应该使用service。Service就是把Pod暴露出来提供服务&#xff0c;Service才是真正的“服务”&#xff0c;它的中文名就叫“服务”。可以说Service是一个应用服务的抽象&#…

【软考中级】系统集成项目管理工程师—导学

软考中级——系统集成项目管理工程师 原视频链接&#xff1a;2024年05月【持续更新】最新系统集成项目管理工程师培训课程-网络课程-软考中级培训 考试介绍 基础介绍 软考全称又叫计算机技术与软件专业技师资格水平考试。 软考是国家人力资源和社会保障部、工业和信息化部…

基于SpringBoot实现的医院药品管理系统

一、系统架构 前端&#xff1a;html | layui | js | css 后端&#xff1a;springboot | mybatis-plus 环境&#xff1a;jdk1.6 | mysql | maven 二、代码及数据库 三、功能介绍 01. 登录页 02. 药品库存管理-登记出入口信息 03. 药品库存管理-问题药品信息 …

【机器学习科学库】全md文档笔记:Jupyter Notebook和Matplotlib使用(已分享,附代码)

本系列文章md笔记&#xff08;已分享&#xff09;主要讨论人工智能相关知识。主要内容包括&#xff0c;了解机器学习定义以及应用场景&#xff0c;掌握机器学习基础环境的安装和使用&#xff0c;掌握利用常用的科学计算库对数据进行展示、分析&#xff0c;学会使用jupyter note…

字符串(算法竞赛)--Manacher(马拉车)算法

1、B站视频链接&#xff1a;F05 Manacher(马拉车)_哔哩哔哩_bilibili 题目链接&#xff1a;【模板】manacher - 洛谷 ​ #include <bits/stdc.h> using namespace std; const int N3e7; char a[N],s[N]; int d[N];//回文半径函数void get_d(char*s,int n){d[1]1;for(int…

SpringBoot:数据访问-整合 Druid 配置数据源监控

点击查看数据访问demo&#xff1a;LearnSpringBoot06DataJdbc 点击查看更多的SpringBoot教程 简介 Druid Spring Boot Starter 用于帮助你在Spring Boot项目中轻松集成Druid数据库连接池和监控。 一、添加druid-spring-boot-starter依赖 点击查询最新版 <dependency&g…

猜字谜|构建生成式 AI 应用实践(一)

在 2023 亚马逊云科技 re:Invent 之后&#xff0c;细心的开发者们也许已经发现有一个很有趣的动手实验&#xff1a;开发一款可部署的基于大语言模型的字谜游戏&#xff1a; 该款游戏使用了文生图模型为玩家提供一个未知的提示词&#xff0c;玩家需要根据模型生成的图像来猜测该…

【基于Ubuntu20.04的Autoware.universe安装过程】方案二:双系统 | 详细记录 | 全过程图文 by.Akaxi

目录 一、Autoware.universe背景 Part-1&#xff1a;安装双系统教程 二、查看Windows引导方式 三、制作安装盘 四、设置电脑配置 1.关闭bitlocker 2.压缩硬盘分区 3.关闭Secure Boot 4.关闭intel RST 5.BIOS设置U盘引导 五、安装Ubuntu20.04 1.ventoy引导 2.安装配…

代码随想录算法训练营Day27 || leetCode 93.复原IP地址 || 78.子集 || 90.子集II

93.复原IP地址 与分割回文串的代码相近&#xff0c;先写出判断函数&#xff0c;之后以判断结果为标准&#xff0c;执行回溯的代码。此题中使用点的个数来决定回溯的终止&#xff0c;需要学习一下。 class Solution { private:vector<string> result;bool isValid(const …

c语言经典测试题5

1.题1 t0; while(printf("*")) { t; if (t<3) break; }关于上述代码描述正确的是&#xff1f; A: 其中循环控制表达式与0等价 B: 其中循环控制表达式与0等价 C: 其中循环控制表达式是不合法的 D: 以上说法都不对 我们来分析一下&#xff1a;printf的返回值…

一个具有强大PDF处理能力的.Net开源项目

PDF具有跨平台、可读性强、不可修改性、无需特定阅读软件、内容安全等好处&#xff0c;在工作中经常都会用到。 所以&#xff0c;我们在项目开发中&#xff0c;经常需要生成PDF的文件&#xff0c;或者把Html、Xml等文件转化为PDF格式。 今天给大家推荐一个具有PDF处理能力的.…

消息队列-RabbitMQ:延迟队列、rabbitmq 插件方式实现延迟队列、整合SpringBoot

十六、延迟队列 1、延迟队列概念 延时队列内部是有序的&#xff0c;最重要的特性就体现在它的延时属性上&#xff0c;延时队列中的元素是希望在指定时间到了以后或之前取出和处理&#xff0c;简单来说&#xff0c;延时队列就是用来存放需要在指定时间被处理的元素的队列。 延…

20240113----重返学习-`nginx/conf/nginx.conf`的https证书配置说明

20240113----重返学习-nginx/conf/nginx.conf的https证书配置说明 文件说明 不同域名的多虚拟主机配置 server {listen 443 ssl;#在443端口上监听SSL/TLS流量;server_name localhost;#指定服务器名称&#xff0c;应该与域名匹配;ssl_certificate fangchaoduan.com.pem;#指定SS…

数据安全之路:深入了解MySQL的行锁与表锁机制

欢迎来到我的博客&#xff0c;代码的世界里&#xff0c;每一行都是一个故事 数据安全之路&#xff1a;深入了解MySQL的行锁与表锁机制 前言基础innodb中锁与索引的关系如何避免表锁 前言 在当今数据密集的应用中&#xff0c;数据库锁成为了确保数据一致性和并发操作的关键工具…

Java 拦截器深入了解学习

Java 拦截器深入了解学习 命运总是不如愿。 但往往是在无数的痛苦中&#xff0c;在重重的矛盾和艰难中&#xff0c;才使人成熟起来&#xff0c;坚强起来;虽然这些东西在实际感受中给人带来的并不都是欢乐。 ————路遥《平凡的世界》 什么是拦截器&#xff08;Interceptor&am…

【C++】C++对C语言的关系,拓展及命名空间的使用

文章目录 &#x1f4dd;C简述C融合了3种不同的编程方式&#xff1a;C和C语言关系是啥呢&#xff1f;C标准 &#x1f320;C应用&#x1f320;C语言优点第一个C程序 &#x1f320;命名空间&#x1f320;命名空间的使用命名空间的定义 &#x1f320;怎么使用命名空间中的内容呢&am…

强化学习入门到不想放弃-3

第三节我们主要讲一下SARSA模型 上节课的链接:强化学习入门到不想放弃-2 (qq.com) 有模型的概念:简单理解,上节课我讲的就是有模型,就是可以开上帝视角,知道全局地图 无模型的概念: 打CS,但是看不到地图的情况,全凭自己探索 今天的讲解环境还是和上节课一样,如下…

Movelt使用笔记-Movelt Setup Assistant

目录 Setup Assistant配置1 Start 加载urdf模型3 Virtual joints 虚拟关节5 Robot Poses 机器人位姿7 Passive Joints 被动关节8 Controllers 控制器9 Simulation 仿真10 3D Perception 3D感知11 Author Information 作者信息12 Configuration Files 配置文件启动MoveIt!Setup…

【Python笔记-设计模式】前端控制器模式

一、说明 常作为MVC&#xff08;Model-View-Controller&#xff09;模式的一部分&#xff0c;用来处理用户请求并将其分发给相应的处理程序&#xff08;即路由匹配&#xff09;。 (一) 解决问题 将请求的处理流程集中管理&#xff0c;统一处理所有的请求 (二) 使用场景 需…