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

基于React18内核探秘,手写 React 高质量源码迈 本文介绍了React18内核探秘的关键要点,包括虚拟DOM和协调算法的实现,以及手写React高质量源码的心得。通过这一过程,我们能够深入理解React的原理和设计思想,并实现技术突破,为前端开发带来创新和进步。本文将带你从0到1实现React18内核,探秘React的核心机制,并分享手写React高质量源码的心得。通过这个过程,我们可以深入理解React的原理和设计思想,实现技术突破并提升自己的开发能力。协调算法:学习React的协调算法(如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;

启动阶段

  • 在前文分析了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版本的流程,区别不大
【React】React18核心源码解读 阅读详情

相关推荐

【React】版本18 源码遨游(六) 车道 Lanes

【React】版本18 源码遨游(六) 车道 Lanes 车道是React调度,协调过程中很重要的一个新概念。今天我们详细过一遍车道的源码。 位置:react-reconciler\src\ReactFiberLane.new.js 车道数量、定义 车道是用位图定义的,它由一个31位的二进制数来保存、指示车道信息。 在低位的 1 的权重大,在高位的 1 权重小。我们看一共定义了哪些车道: // 一共有31条车道 export const TotalLanes = 31; // 表示没有车道,所有位都为0

ljinest 1048

(万字长文)React 18 源码与原理解读 —— 看这一篇就够了

本节是整个React 的源码与原理解读解读教程的最后一章,作者会用自己的理解带着大家梳理一下整个 React 的运行过程,因为篇幅的问题,作者会主要讲述一下一下重点的内容和要点的信息,侧重给读者一个完整的运行逻辑,对于讲解不到位的部分可以阅读之前的详细教程,教程肯定还是会有疏漏的部分请大家海涵

weixin_46463785的博客 7355

React Fiber流程图(前端)

Fiber是一种虚拟的、轻量级的工作单元,它代表了React组件树中的一个节点。React Fiber通过构建一个Fiber树来表示整个组件层级结构,从而实现对组件的高效更新和渲染。React Fiber的流程图包括了初始化阶段、协调阶段和渲染阶段,每个阶段都有特定的任务和步骤。React Fiber的初始化阶段主要是创建Fiber树,并为每个组件创建对应的Fiber节点。协调阶段是React Fiber的核心阶段,它负责确定需要更新的组件,并构建更新的Fiber树。的值,并重新渲染组件。

ZdqDeveloper的博客 525

【React】React18.2.0核心源码解读

【React】React18.2.0核心源码解读

owo_ovo的博客 4232

React中的高优先级任务插队机制

在React的concurrent模式下,低优先级任务执行过程中,一旦有更高优先级的任务进来,那么这个低优先级的任务会被取消,优先执行高优先级任务。等高优先级任务做完了,低优先级任务会被重新做一遍。 我们用一个具体的例子来理解一下高优先级任务插队。 有这样一个组件,state为0,进入页面,会调用setState将state加1,这个作为低优先级任务。React开始进行更新,在这个低优先级任务尚未完成时,模拟按钮点击,state加2,这个作为高优先级任务。可以看到,页面上的数字变化为0 -> 2

与其临渊羡鱼,不如退而结网 1425

UE5 C++教程(一、基本结构)

虚幻API由三块组成,分别是Runtime,Developer utilitiew,functionality for the Editor,大多数游戏性编程只使用运行时模块,最常见的三个是Corn、CornUObject和Engine。

skycol的博客 1万+

react源码解析18事件系统

react源码解析18事件系统 视频讲解(高效学习):进入学习 往期文章: 1.开篇介绍和面试题 2.react的设计理念 3.react源码架构 4.源码目录结构和调试 5.jsx&核心api 6.legacy和concurrent模式入口函数 7.Fiber架构 8.render阶段 9.diff算法 10.commit阶段 11.生命周期 12.状态更新流程 13.hooks源码 14.手写hooks 15.scheduler&Lane 16.concurrent模式 17.conte

潇晨1998的博客 170

hpm6750evk2开发板(一)菜鸟入门

hpm6750与我们平时常用的stm单片机在开发环境和开发方法上有很大的区别,所以上手还是需要花一点时间的,拿到板子先不要急着上电调试,我们先找资料学习一下了,免得多走冤枉路,以下是我整理的一些学习资源,包括开发板使用手册、芯片SDK、开发工具、视频资料等,建议先把使用手册和学习视频先看了,再取搭建环境测试。最近刚入手一块hpm6750evk2开发板,花了几天时间学习,算是菜鸟入门了,今天分享一下学习过程。看完资料后,把开发工具安装上,下一篇文章我们分享一下调试工程的过程和遇到的问题。

Silas1750的博客 3088

LLaVA多模态实战入门:从零部署视觉语言模型

视觉语言模型(VLM)是实现图像理解与自然语言生成协同的关键技术,其核心在于跨模态对齐——将图像特征映射到语言空间。LLaVA作为轻量级开源方案,依托CLIP视觉编码器与Qwen/Vicuna等语言模型,通过可训练投影层(如MLP或Q-Former)实现高效语义桥接,显著降低多模态应用门槛。相比GPT-4V等大模型,它在有限算力(如RTX 3090、Jetson Orin)下仍能完成图文问答、缺陷识别、医疗描述等任务,具备强工程落地性。本文聚焦LLaVA-1.5/NeXT等主流变体,覆盖环境配置、模型加载、

aefg95955的博客 573

基于Lora的一主多从监测与控制系统

题目:基于Lora的远程农业监测与控制系统 目的:通过Lora子节点采集好各种传感器的信息,发送给汇聚节点,汇聚节点将数据通过ESP8266上传至云端,使用了微信小程序进行监测与控制

热爱生活、奋斗前行 7841

告别DLTS的模糊地带:手把手教你用拉普拉斯深能级瞬态光谱(LDLTS)精准揪出半导体缺陷

本文详细介绍了拉普拉斯深能级瞬态光谱(LDLTS)技术在半导体缺陷检测中的高分辨率解决方案。相比传统DLTS,LDLTS通过拉普拉斯变换和正则化算法,将分辨率提升至1meV,有效解析复杂重叠缺陷谱,适用于硅、GaN等材料的精确缺陷分析。文章涵盖LDLTS原理、系统搭建、测量流程及工业应用案例,为半导体工艺优化提供强大工具。

weixin_29238147的博客 193

前端跳槽必备源码剖析:核心揭秘React 18

在开始源码剖析之前,我们先来简要回顾一下React 18的主要特性。React 18带来了Suspense、Concurrent Mode、Start Transition等新特性,为开发者们提供了更强大的性能优化能力和更灵活的组件加载方式。同时,React 18还对原有的API进行了改进和优化,使得开发体验更加流畅。通过本文的深入剖析和实战技巧分享,相信你已经对React 18的底层原理和实战应用有了更深入的了解。希望这些知识和技巧能够助你在前端领域取得更大的突破和成功!

xuxian的博客 1083

计组实验之四路表决器

请在之前四路表决器电路实现的基础上,继续用连接线 联接CD4511,将投票为1的票数总和显示到一个 LED上,思考如何做?由于我们需要通过显示字形来直接看出有几个1,在这里,我们最多有四个1,即显示字形最多到4就行,所以我们需要将有几个1用2进制数来表示,下面给出了相关电路图。显示输入插口,按真值表(自拟)要求,逐次改变输入变量, 测量相应的输出值,验证逻辑功能, 与表3-1 进行比较, 验证所设计的逻辑电路是否符合要求。用实验结果与真值表3-1和3-2进行比对,可以得出,我们实验结果是正确的。

szyugly的博客 1万+

React源码解读(三)React调度过程

这个文件的核心功能是管理 React 应用中多个根节点(FiberRoot)的调度任务,确保各个根节点的更新工作能按照一定的优先级和规则进行处理。跟踪哪些根节点有待处理的工作。安排微任务来处理根节点的调度任务。处理同步工作的刷新,确保高优先级的任务能及时得到执行。当一个根节点收到更新时,函数会将其添加到调度列表中,并安排一个微任务来处理调度任务。微任务会调用函数,该函数会遍历所有调度中的根节点,为每个根节点安排具体的任务。最后,会调用函数刷新同步工作,确保高优先级的任务能及时得到执行。

风过疏竹竹不留声,雁渡寒潭潭不留影,故君子事来心始现,事去而心随空 1992

C# 读取图片和扫描PDF中的文本

本博客将探讨如何使用C#和OCR库实现从图片和扫描的 PDF 中提取文本。Spire.OCR for .NET 是一个OCR文字识别库,专为在 .NET 应用程序中识别图片的文本而设计。首先,使用 Spire.PDF 将扫描的 PDF 转换为图片,然后利用 Spire.OCR 从这些图片中提取文本。由于该OCR库并不支持直接处理PDF,因此要从扫描的 PDF 中提取文本,我们首先需要将 PDF 文档转换为图片。转换完成后,再利用 Spire.OCR 从生成的图片中提取文本。方法来识别图片中的文本。

nuclear2011的博客 5405

RTKLIB使用方法(喂饭版)(三)——RTKPOST

应用OTL校正需要在“files”的“ocean loading BLQ format”中配置OTL文件。应用极潮需要在“files”的“EOP data file”配置ERP(地球旋转参数)文件。RAIM FDE:设置是否启用RAIM(接收机自主完整性监控)FDE(故障检测和排除)功能。点击下方的“save”可以把配置保存成文件,点击“load”就可以把配置文件加载出来。4接收机动态模型/地球潮汐矫正:设置流动站接收机动力学模型,只可以使用在DGPS/DGNSS或动态模型中。选择流动站天线的类型。

A2020713的博客 1万+

一文搞懂React18原理

可能一直在写代码的路上,大多数时候就是个工具人。今天一起看看react18的实现原理,持续学习中。

相信自己,没有攀不过去的高山! 1801

博途PLC模糊PID控制(量化因子和比例因子)

参数"量化因子和比例因子",都是为了对清晰值进行比例变换而设置的,其作用是使变量按一定比例进行放大或缩小,以便和我们的模糊论域相匹配完成一一映射。这种变换对整个系统的工作性能也是有影响的。(其实相当于额外引入了比例作用,所以说模糊PID的调节是个系统工程)。限于本人能力,文中难免出现错误和不足,欢迎大家批评和指正。博途PLC的模糊PID控制完整算法可以参看下面这篇文章博途PLC模糊PID控制详解(SCL完整源代码+参数)_RXXW_Dor的博客-优快云博客。......

RXXW_Dor的博客 5272

【React架构 - Fiber构造循环】

React架构中两大事件循环之一在协调器Reconciler中的Fiber构造循环

weixin_44273311的博客 2323

蓝牙模块HM-10手册

蓝牙模块HM-10手册,该手册对蓝牙模块HM-10进行了详细的介绍和使用方法以及一些AT指令。

数学建模模型总结

这是本人在两年内准备参加数学建模竞赛搜集到的模型资料,已经经过了本人的筛选和整理,希望能够对准备参加数学建模竞赛的学弟学妹们提供一些帮助!

React17源码深入解析+React18源码初探,进阶必备

6月份,React 发布了v18 的Alpha 版本。引起了前端圈的一片哀嚎:肝不动了!17还没学会,18就要出来了!字里行间透露出拒绝。但是说归说,该学还是得学。React 18预计将会...

程序员成长指北 1074

汉书HE8,好用的汇编代码编写工具

汉书HE8,好用的汇编代码编写工具早期编写汇编代码所用功能强大

【React】版本18 代码遨游(十一)任务调度入口函数

任务调度入口函数 文章目录任务调度入口函数*performSyncWorkOnRoot*(root)*performConcurrentWorkOnRoot*(root, didTimeout)*flushSyncCallbacks*()*performUnitOfWork*(unitOfWork: Fiber)*completeUnitOfWork*(unitOfWork: Fiber)*renderRootSync*(root: FiberRoot, lanes: Lanes)*workLoopSync

ljinest 802

OV9734 DATASHEET,完整规格书

OV9734完整规格书,包括电气参数,尺寸规格,寄存器配置表等

react最新fiber架构原理和流程

react16以后做了很大的改变,对diff算法进行了重写,从总体看,主要是把一次计算,改变为多次计算,在浏览器有高级任务时,暂停计算。 原理:从Stack Reconciler到Fiber Reconciler,源码层面其实就是干了一件递归改循环的事情 fiber设计目的:解决由于大量计算导致浏览器掉帧现象。 由于js是单线程的,解决主线程被长时间计算占用的问题,就是将计算分为多个步骤,分...

前端361 3339

【React 源码】(八)fiber 树构造(基础准备)

本节是fiber树构造的准备篇, 首先在宏观上从不同的视角(任务调度循环,fiber树构造循环)介绍了fiber树构造在React体系中所处的位置, 然后深入包分析fiber树构造过程中需要使用到的全局变量, 并解读了双缓冲技术和优先级(车道模型)的使用, 最后解释栈帧管理的实现细节. 有了这些基础知识,fiber树构造的具体实现过程会更加简单清晰.

weixin_44828588的博客 773

React中的4种优先级

来源 |http://www.fly63.com/article/detial/10072UI产生交互的根本原因是各种事件,这也就意味着事件与更新有着直接关系。不同事件产生的更新,它们...

web前端开发 2542

Fix signatures do not match the previously installed version

报错信息FAILURE: Build failed with an exception.* What went wrong: Execution failed for task ':app:installDebug'. > com.android.builder.testing.api.DeviceException: com.android.ddmlib.InstallException: Fai

Wang的专栏 2万+

在react中 实现 base64的图片转换成formdata格式并实现上传功能

如果不想要服务端实现处理base64格式的图片的转换,那么在浏览器端来实现,部分属于伪代码,仅作为开发参考,请勿直接拷贝使用

Wang的专栏 8836
上一篇: React18源码: Fiber树中的优先级与帧栈模型
下一篇: Nestjs框架: 日志和官方日志
Wang's Blog
博客等级 码龄12年 7434粉丝 2421原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Wang's Blog

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值