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;

启动阶段

  • 在前文分析了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的优先级,初次构造返回 NoLaneslanes = 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 yieldwhile (workInProgress !== null && !shouldYield()) {performUnitOfwork(workInProgress);}
    }
    
  • 可以看到workLoopConcurrent相比于Sync,会多一个停顿机制

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

  • 结合 performUnitOfWork 函数

    // ...省略部分无关代码
    function performUnitOfWork(unitofwork: Fiber): void {// unitOfWork 就是被传入的 workInProgressconst current = unitOfWork.alternate;let next;next = beginWork(current, unitOfWork, subtreeRenderLanes);unitOfWork.memoizedProps = unitOfWork.pendingProps;if (next === null) {// 如果没有派生出新的节点,则进入completeWork阶段,传入的是当前unitOfWorkcompleteUnitOfWork(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即可,它没有 memoizedStateconst 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类型的fibernextChildren = null;} else if (prevProps != null && shouldSetTextContent(type, prevProps)) {// 特殊操作需要设置fiber.flagsworkInProgress.flags |= ContentReset;}// 特殊操作需要设置fiber.flagsmarkRef(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读取的,所以略过PerformedWorkif (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 = RootCompletedif (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.mzph.cn/news/703745.shtml

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

相关文章

面试经典150题【31-40】

文章目录 面试经典150题【31-40】76.最小覆盖字串36.有效的数独54.螺旋矩阵48.旋转图像73.矩阵置零289.生命游戏383.赎金信205.同构字符串290.单词规律242.有效的字母异位词 面试经典150题【31-40】 76.最小覆盖字串 基本思路很简单&#xff0c;就是先移动右边到合适位置。再移…

面对耦合度过高的第三方对接

今天接到一个需求&#xff0c;原型图如下&#xff1a;抖音数据&#xff08;给的是数据占比&#xff09;&#xff0c;要求根据性别&#xff0c;年龄&#xff0c;类目&#xff0c;地域&#xff0c;消费偏好&#xff0c;客单价筛选后排序。 1、性别给的示例&#xff1a;{ "…

Rem 自适应原理与应用

前言 移动端适配有很多方案&#xff0c;这篇文章将根据 rem 自适应的原理进行讲解&#xff0c;接下来跟着作者的思路一起来看看吧&#xff01; 原理 在搞清楚 rem 适配之前&#xff0c;我们先来了解一下什么是 rem&#xff1f; rem 是一种相对长度单位&#xff0c;它相对于根…

51单片机学习(5)-----蜂鸣器的介绍与使用

前言&#xff1a;感谢您的关注哦&#xff0c;我会持续更新编程相关知识&#xff0c;愿您在这里有所收获。如果有任何问题&#xff0c;欢迎沟通交流&#xff01;期待与您在学习编程的道路上共同进步。 目录 一. 蜂鸣器的介绍 1.蜂鸣器介绍 2.压电式蜂鸣器 &#xff08;无源…

LeetCode 第一题: 两数之和

文章目录 第一题: 两数之和题目描述示例 解题思路Go语言实现 - 一遍哈希表法C实现算法分析 排序和双指针法Go语言实现 - 排序和双指针法C算法分析 暴力法Go语言实现 - 暴力法C算法分析 二分搜索法Go语言实现 - 二分搜索法C算法分析 第一题: 两数之和 ‍ 题目描述 给定一个整…

6.2 认证授权模块 - 继承JWT、网关认证

认证授权模块 - 继承JWT、网关认证 文章目录 认证授权模块 - 继承JWT、网关认证一、JWT1.1 普通令牌1.2 JWT 令牌介绍1.3 测试生成JWT 令牌1.3.1 TokenConfig1.3.2 授权服务器配置 AuthorizationServer1.3.3 WebSecurityConfig 安全管理配置1.3.4 返回信息 1.4 资源服务集成 JW…

《乱弹篇(十九)围炉诗话》

闲来无事&#xff0c;近日浏览中国古代文学书藉&#xff0c;读罢《新嫁娘词》中的一首五言绝句诗&#xff0c;觉得妙趣横生&#xff0c;艺术造诣甚高。今天恰逢春潮寒意还浓&#xff0c;气温很低&#xff0c;笔者雅兴勃发&#xff0c;便开足暖气&#xff0c;也附庸风雅地来一番…

WSL2配置Linux、Docker、VS Code、zsh、oh my zsh(附Docker开机自启设置)

0. 写在前面 本篇笔记来自于UP主麦兜搞IT的合集视频Windows10开发环境搭建中的部分内容 1. 安装WSL2 按照微软官方文档进行操作&#xff0c;当然也可以直接wsl --install 也可以按照 旧版手动安装的步骤 来进行操作 选择安装的是Ubuntu 20.04 LTS 注&#xff1a;WSL默认安装…

抖音视频提取软件使用功能|抖音视频下载工具

我们的抖音视频提取软件是一款功能强大、易于操作的工具&#xff0c;旨在解决用户在获取抖音视频时需要逐个复制链接、下载的繁琐问题。我们的软件支持通过关键词搜索和分享链接两种方式获取抖音视频&#xff0c;方便用户快速找到自己感兴趣的内容。 主要功能模块&#xff1a;…

计算机设计大赛 深度学习图像风格迁移

文章目录 0 前言1 VGG网络2 风格迁移3 内容损失4 风格损失5 主代码实现6 迁移模型实现7 效果展示8 最后 0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; 深度学习图像风格迁移 - opencv python 该项目较为新颖&#xff0c;适合作为竞赛课题…

pytorch -- CIFAR10 完整的模型训练套路

网络结构 代码 # CIFAR 10完整的模型训练套路&#xff1a; import torch.optim import torchvision from torch import nn from torch.utils.data import DataLoader from torch.utils.tensorboard import SummaryWriterfrom model import *# 1. 准备数据集 train_data torch…

SSM框架学习笔记07 | Spring MVC入门

文章目录 1. HTTP协议2. Spring MVC2.1. 三层架构2.2. MVC&#xff08;解决表现层的问题&#xff09;2.3. 核心组件 3. Thymeleaf3.1. 模板引擎3.2. Thymeleaf3.3. 常用语法 代码 1. HTTP协议 网址&#xff1a;https://www.ietf.org/ &#xff08;官网网址&#xff09; https:…

dolphinscheduler单机版部署教程

文章目录 前言一、安装准备1. 安装条件2. 安装jdk3. 安装MySQL 二、安装dolphinscheduler1. 下载并解压dolphinscheduler2. 修改配置文件2.1 修改 dolphinscheduler_env.sh 文件2.2 修改 application.yaml 文件 3. 配置mysql数据源3.1 修改MySQL安全策略3.2 查看数据库3.3 创建…

使用Docker部署MinIO并结合内网穿透实现远程访问本地数据

文章目录 前言1. Docker 部署MinIO2. 本地访问MinIO3. Linux安装Cpolar4. 配置MinIO公网地址5. 远程访问MinIO管理界面6. 固定MinIO公网地址 前言 MinIO是一个开源的对象存储服务器&#xff0c;可以在各种环境中运行&#xff0c;例如本地、Docker容器、Kubernetes集群等。它兼…

Pytest教程:一种利用 Python Pytest Hook 机制的软件自动化测试网络数据抓包方法

随着计算机技术的发展&#xff0c;使得网络应用的数量不断增加&#xff0c;因此网络数据抓包成为了网络应用开发和测试中非常重要的一部分。目前&#xff0c;已有许多网络数据抓包工具可供使用&#xff0c;例如 Wireshark、Tcpdump、Fiddler 等&#xff0c;但这些工具需要手动配…

进程的学习

进程基本概念: 1.进程: 程序&#xff1a;存放在外存中的一段数据组成的文件 进程&#xff1a;是一个程序动态执行的过程,包括进程的创建、进程的调度、进程的消亡 2.进程相关命令: 1.top 动态查看当前系统中的所有进程信息&#xff08;根据CPU占用率排序&#xf…

PHY6222系统级SOC蓝牙芯片低功耗高性能蓝牙MESH组网智能家居

简介 PHY6222是一款支持BLE 5.2功能和IEEE 802.15.4通信协议的系统级芯片&#xff08;SoC&#xff09;&#xff0c;集成了超低功耗的高性能多模射频收发机&#xff0c;搭载32-bit ARM?Cortex?-M0处理器&#xff0c;提供64K retention SRAM、可选128K-8M Flash、96KB ROM以及2…

十、线性代数二-线性相关

目录 1、线性相关的概念&#xff1a; 2、线性相关的代数表示&#xff1a; 3、线性相关的判断方法&#xff1a; 理解&#xff1a;线性相关指的是 向量组&#xff08;α1&#xff0c;α2&#xff0c;α3&#xff0c;...&#xff09;的 秩是 小于 k 的元数的&#xff0c;即齐次…

第二节:Vben Admin 登录逻辑梳理和对接后端准备

系列文章目录 上一节&#xff1a;第一节&#xff1a;Vben Admin介绍和初次运行 文章目录 系列文章目录前言项目路径的概述一、登录逻辑梳理loginApi接口查看Mock 二、后端程序对接准备关闭Mock 总结 前言 第一节&#xff0c;我们已经配置了前端环境&#xff0c;运行起来了我们…

文献速递:深度学习--深度学习方法用于帕金森病的脑电图诊断

文献速递&#xff1a;深度学习–深度学习方法用于帕金森病的脑电图诊断 01 文献速递介绍 人类大脑在出生时含有最多的神经细胞&#xff0c;也称为神经元。这些神经细胞无法像我们身体的其他细胞那样自我修复。随着年龄的增长&#xff0c;神经元逐渐死亡&#xff0c;因此变得…