• 【react 源码】(九)fiber 树构造(初次创建) 难 难 难


    fiber 树构造(初次创建)

    本节的内容完全建立在前文fiber 树构造(基础准备)中介绍的基础知识之上, 其中总结了fiber 树构造的 2 种情况:

    1. 初次创建: 在React应用首次启动时, 界面还没有渲染, 此时并不会进入对比过程, 相当于直接构造一棵全新的树.
    2. 对比更新: React应用启动后, 界面已经渲染. 如果再次发生更新, 创建新fiber之前需要和旧fiber进行对比. 最后构造的 fiber 树有可能是全新的, 也可能是部分更新的.

    本节只讨论初次创建这种情况, 为了控制篇幅(本节直击核心源码, 不再介绍基础知识, 可参照fiber 树构造(基础准备))并突出fiber 树构造过程, 后文会在Legacy模式下进行分析(因为只讨论fiber树构造原理, Concurrent模式与Legacy没有区别).

    本节示例代码如下(codesandbox 地址):

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

    启动阶段

    在前文React 应用的启动过程中分析了 3 种启动模式的差异, 在进入react-reconciler包之前(调用updateContainer之前), 内存状态图如下:

    根据这个结构, 可以在控制台中打出当前页面对应的fiber树(用于观察其结构):

     
    

    document.getElementById('root')._reactRootContainer._internalRoot.current;

    然后进入react-reconciler包调用updateContainer 函数:

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

    由于update对象的创建, 此时的内存结构如下:

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

    构造阶段

    为了突出构造过程,排除干扰,先把内存状态图中的FiberRootHostRootFiber单独提出来(后文在此基础上添加):

    scheduleUpdateOnFiber 函数中:

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

    可以看到, 在Legacy模式下且首次渲染时, 有 2 个函数markUpdateLaneFromFiberToRootperformSyncWorkOnRoot.

    其中markUpdateLaneFromFiberToRoot(fiber, lane)函数在fiber树构造(对比更新)中才会发挥作用, 因为在初次创建时并没有与当前页面所对应的fiber树, 所以核心代码并没有执行, 最后直接返回了FiberRoot对象.

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

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

    其中getNextLanes返回本次 render 的渲染优先级(详见fiber 树构造(基础准备)优先级相关小节)

    renderRootSync

     
    

    renderRootSync中, 在执行fiber树构造前(workLoopSync)会先刷新栈帧prepareFreshStack(参考fiber 树构造(基础准备)).在这里创建了HostRootFiber.alternate, 重置全局变量workInProgressworkInProgressRoot等.

    循环构造

    逻辑来到workLoopSync, 虽然本节在Legacy模式下进行讨论, 此处还是对比一下workLoopConcurrent

     
    
    1. function workLoopSync() {
    2. while (workInProgress !== null) {
    3. performUnitOfWork(workInProgress);
    4. }
    5. }
    6. function workLoopConcurrent() {
    7. // Perform work until Scheduler asks us to yield
    8. while (workInProgress !== null && !shouldYield()) {
    9. performUnitOfWork(workInProgress);
    10. }
    11. }

    可以看到workLoopConcurrent相比于Sync, 会多一个停顿机制, 这个机制实现了时间切片可中断渲染(参考React 调度原理)

    结合performUnitOfWork函数(源码地址)

     
    
    1. // ... 省略部分无关代码
    2. function performUnitOfWork(unitOfWork: Fiber): void {
    3. // unitOfWork就是被传入的workInProgress
    4. const current = unitOfWork.alternate;
    5. let next;
    6. next = beginWork(current, unitOfWork, subtreeRenderLanes);
    7. unitOfWork.memoizedProps = unitOfWork.pendingProps;
    8. if (next === null) {
    9. // 如果没有派生出新的节点, 则进入completeWork阶段, 传入的是当前unitOfWork
    10. completeUnitOfWork(unitOfWork);
    11. } else {
    12. workInProgress = next;
    13. }
    14. }

    可以明显的看出, 整个fiber树构造是一个深度优先遍历(可参考React 算法之深度优先遍历), 其中有 2 个重要的变量workInProgresscurrent(可参考前文fiber 树构造(基础准备)中介绍的双缓冲技术):

    • workInProgresscurrent都视为指针
    • 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函数(如: updateHostRootupdateClassComponent 等)的主要逻辑:

    1. 根据 ReactElement对象创建所有的fiber节点, 最终构造出fiber树形结构(设置returnsibling指针)
    2. 设置fiber.flags(二进制形式变量, 用来标记 fiber节点 的增,删,改状态, 等待completeWork阶段处理)
    3. 设置fiber.stateNode局部状态(如Class类型节点: fiber.stateNode=new Class())
     
    
    1. function beginWork(
    2. current: Fiber | null,
    3. workInProgress: Fiber,
    4. renderLanes: Lanes,
    5. ): Fiber | null {
    6. const updateLanes = workInProgress.lanes;
    7. if (current !== null) {
    8. // update逻辑, 首次render不会进入
    9. } else {
    10. didReceiveUpdate = false;
    11. }
    12. // 1. 设置workInProgress优先级为NoLanes(最高优先级)
    13. workInProgress.lanes = NoLanes;
    14. // 2. 根据workInProgress节点的类型, 用不同的方法派生出子节点
    15. switch (
    16. workInProgress.tag // 只保留了本例使用到的case
    17. ) {
    18. case ClassComponent: {
    19. const Component = workInProgress.type;
    20. const unresolvedProps = workInProgress.pendingProps;
    21. const resolvedProps =
    22. workInProgress.elementType === Component
    23. ? unresolvedProps
    24. : resolveDefaultProps(Component, unresolvedProps);
    25. return updateClassComponent(
    26. current,
    27. workInProgress,
    28. Component,
    29. resolvedProps,
    30. renderLanes,
    31. );
    32. }
    33. case HostRoot:
    34. return updateHostRoot(current, workInProgress, renderLanes);
    35. case HostComponent:
    36. return updateHostComponent(current, workInProgress, renderLanes);
    37. case HostText:
    38. return updateHostText(current, workInProgress);
    39. case Fragment:
    40. return updateFragment(current, workInProgress, renderLanes);
    41. }
    42. }

    updateXXX函数(如: updateHostRoot, updateClassComponent 等)虽然 case 较多, 但是主要逻辑可以概括为 3 个步骤:

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

    不同的updateXXX函数处理的fiber节点类型不同, 总的目的是为了向下生成子节点. 在这个过程中把一些需要持久化的数据挂载到fiber节点上(如fiber.stateNode,fiber.memoizedState等); 把fiber节点的特殊操作设置到fiber.flags(如:节点ref,class组件的生命周期,function组件的hook,节点删除等).

    这里列出updateHostRootupdateHostComponent的代码, 对于其他常用 case 的分析(如class类型, function类型), 在状态组件章节中进行探讨.

    fiber树的根节点是HostRootFiber节点, 所以第一次进入beginWork会调用updateHostRoot(current, workInProgress, renderLanes)

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

    普通 DOM 标签类型的节点(如div,span,p),会进入updateHostComponent:

     
    
    1. // ...省略部分无关代码
    2. function updateHostComponent(
    3. current: Fiber | null,
    4. workInProgress: Fiber,
    5. renderLanes: Lanes,
    6. ) {
    7. // 1. 状态计算, 由于HostComponent是无状态组件, 所以只需要收集 nextProps即可, 它没有 memoizedState
    8. const type = workInProgress.type;
    9. const nextProps = workInProgress.pendingProps;
    10. const prevProps = current !== null ? current.memoizedProps : null;
    11. // 2. 获取下级`ReactElement`对象
    12. let nextChildren = nextProps.children;
    13. const isDirectTextChild = shouldSetTextContent(type, nextProps);
    14. if (isDirectTextChild) {
    15. // 如果子节点只有一个文本节点, 不用再创建一个HostText类型的fiber
    16. nextChildren = null;
    17. } else if (prevProps !== null && shouldSetTextContent(type, prevProps)) {
    18. // 特殊操作需要设置fiber.flags
    19. workInProgress.flags |= ContentReset;
    20. }
    21. // 特殊操作需要设置fiber.flags
    22. markRef(current, workInProgress);
    23. // 3. 根据`ReactElement`对象, 调用`reconcileChildren`生成`Fiber`子节点(只生成`次级子节点`)
    24. reconcileChildren(current, workInProgress, nextChildren, renderLanes);
    25. return workInProgress.child;
    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 对象的副作用队列(firstEffectlastEffect)添加到父节点的副作用队列之后, 更新父节点的firstEffectlastEffect指针.
    3. 识别beginWork阶段设置的fiber.flags, 判断当前 fiber 是否有副作用(增,删,改), 如果有, 需要将当前 fiber 加入到父节点的effects队列, 等待commit阶段处理.
     
    
    1. function completeUnitOfWork(unitOfWork: Fiber): void {
    2. let completedWork = unitOfWork;
    3. // 外层循环控制并移动指针(`workInProgress`,`completedWork`等)
    4. do {
    5. const current = completedWork.alternate;
    6. const returnFiber = completedWork.return;
    7. if ((completedWork.flags & Incomplete) === NoFlags) {
    8. let next;
    9. // 1. 处理Fiber节点, 会调用渲染器(调用react-dom包, 关联Fiber节点和dom对象, 绑定事件等)
    10. next = completeWork(current, completedWork, subtreeRenderLanes); // 处理单个节点
    11. if (next !== null) {
    12. // 如果派生出其他的子节点, 则回到`beginWork`阶段进行处理
    13. workInProgress = next;
    14. return;
    15. }
    16. // 重置子节点的优先级
    17. resetChildLanes(completedWork);
    18. if (
    19. returnFiber !== null &&
    20. (returnFiber.flags & Incomplete) === NoFlags
    21. ) {
    22. // 2. 收集当前Fiber节点以及其子树的副作用effects
    23. // 2.1 把子节点的副作用队列添加到父节点上
    24. if (returnFiber.firstEffect === null) {
    25. returnFiber.firstEffect = completedWork.firstEffect;
    26. }
    27. if (completedWork.lastEffect !== null) {
    28. if (returnFiber.lastEffect !== null) {
    29. returnFiber.lastEffect.nextEffect = completedWork.firstEffect;
    30. }
    31. returnFiber.lastEffect = completedWork.lastEffect;
    32. }
    33. // 2.2 如果当前fiber节点有副作用, 将其添加到子节点的副作用队列之后.
    34. const flags = completedWork.flags;
    35. if (flags > PerformedWork) {
    36. // PerformedWork是提供给 React DevTools读取的, 所以略过PerformedWork
    37. if (returnFiber.lastEffect !== null) {
    38. returnFiber.lastEffect.nextEffect = completedWork;
    39. } else {
    40. returnFiber.firstEffect = completedWork;
    41. }
    42. returnFiber.lastEffect = completedWork;
    43. }
    44. }
    45. } else {
    46. // 异常处理, 本节不讨论
    47. }
    48. const siblingFiber = completedWork.sibling;
    49. if (siblingFiber !== null) {
    50. // 如果有兄弟节点, 返回之后再次进入`beginWork`阶段
    51. workInProgress = siblingFiber;
    52. return;
    53. }
    54. // 移动指针, 指向下一个节点
    55. completedWork = returnFiber;
    56. workInProgress = completedWork;
    57. } while (completedWork !== null);
    58. // 已回溯到根节点, 设置workInProgressRootExitStatus = RootCompleted
    59. if (workInProgressRootExitStatus === RootIncomplete) {
    60. workInProgressRootExitStatus = RootCompleted;
    61. }
    62. }
    63. 接下来分析fiber处理函数completeWork
    64. function completeWork(
    65. current: Fiber | null,
    66. workInProgress: Fiber,
    67. renderLanes: Lanes,
    68. ): Fiber | null {
    69. const newProps = workInProgress.pendingProps;
    70. switch (workInProgress.tag) {
    71. case ClassComponent: {
    72. // Class类型不做处理
    73. return null;
    74. }
    75. case HostRoot: {
    76. const fiberRoot = (workInProgress.stateNode: FiberRoot);
    77. if (fiberRoot.pendingContext) {
    78. fiberRoot.context = fiberRoot.pendingContext;
    79. fiberRoot.pendingContext = null;
    80. }
    81. if (current === null || current.child === null) {
    82. // 设置fiber.flags标记
    83. workInProgress.flags |= Snapshot;
    84. }
    85. return null;
    86. }
    87. case HostComponent: {
    88. popHostContext(workInProgress);
    89. const rootContainerInstance = getRootHostContainer();
    90. const type = workInProgress.type;
    91. if (current !== null && workInProgress.stateNode != null) {
    92. // update逻辑, 初次render不会进入
    93. } else {
    94. const currentHostContext = getHostContext();
    95. // 1. 创建DOM对象
    96. const instance = createInstance(
    97. type,
    98. newProps,
    99. rootContainerInstance,
    100. currentHostContext,
    101. workInProgress,
    102. );
    103. // 2. 把子树中的DOM对象append到本节点的DOM对象之后
    104. appendAllChildren(instance, workInProgress, false, false);
    105. // 设置stateNode属性, 指向DOM对象
    106. workInProgress.stateNode = instance;
    107. if (
    108. // 3. 设置DOM对象的属性, 绑定事件等
    109. finalizeInitialChildren(
    110. instance,
    111. type,
    112. newProps,
    113. rootContainerInstance,
    114. currentHostContext,
    115. )
    116. ) {
    117. // 设置fiber.flags标记(Update)
    118. markUpdate(workInProgress);
    119. }
    120. if (workInProgress.ref !== null) {
    121. // 设置fiber.flags标记(Ref)
    122. markRef(workInProgress);
    123. }
    124. return null;
    125. }
    126. }
    127. }

    可以看到在满足条件的时候也会设置fiber.flags, 所以设置fiber.flags并非只在beginWork阶段.

    过程图解

    针对本节的示例代码, 将整个fiber树构造过程表示出来:

    构造前:

    在上文已经说明, 进入循环构造前会调用prepareFreshStack刷新栈帧, 在进入fiber树构造循环之前, 保持这这个初始化状态:

    performUnitOfWork第 1 次调用(只执行beginWork):

    • 执行前: workInProgress指针指向HostRootFiber.alternate对象, 此时current = workInProgress.alternate指向fiberRoot.current是非空的(初次构造, 只在根节点时, current非空).
    • 执行过程: 调用updateHostRoot
    • 执行后: 返回下级节点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 次调用(执行beginWorkcompleteUnitOfWork):

    • 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 次调用(执行beginWorkcompleteUnitOfWork):与第 4 次调用中创建fiber(header)节点的逻辑一致. 先后会执行beginWorkcompleteUnitOfWork, 最后构造 DOM 实例, 并将把workInProgress指针指向下一个兄弟节点fiber(p).

    performUnitOfWork第 7 次调用(执行beginWorkcompleteUnitOfWork):

    • beginWork执行过程: 与上次调用中创建fiber(p)节点的逻辑一致
    • completeUnitOfWork执行过程: 以fiber(p)为起点, 向上回溯

    第 1 次循环:

    1. 执行completeWork函数: 创建fiber(p)节点对应的DOM实例, 并append子树节点的DOM实例
    2. 上移副作用队列: 由于本节点fiber(p)没有副作用, 所以执行之后副作用队列没有实质变化(目前为空).
    3. 向上回溯: 由于没有兄弟节点, 把workInProgress指针指向父节点fiber()

    第 2 次循环:

    1. 执行completeWork函数: class 类型的节点不做处理
    2. 上移副作用队列:
      • 本节点fiber()flags标志位有改动(completedWork.flags > PerformedWork), 将本节点添加到父节点(fiber(div))的副作用队列之后(firstEffectlastEffect属性分别指向副作用队列的首部和尾部).
    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)的副作用队列之后.
      • 最后队列的顺序是子节点在前, 本节点在后
    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.finishedWorkfiberRoot.current指针,在commitRoot阶段会进行处理):

    总结

    本节演示了初次创建fiber树的全部过程, 跟踪了创建过程中内存引用的变化情况. fiber树构造循环负责构造新的fiber树, 构造过程中同时标记fiber.flags, 最终把所有被标记的fiber节点收集到一个副作用队列中, 这个副作用队列被挂载到根节点上(HostRootFiber.alternate.firstEffect). 此时的fiber树和与之对应的DOM节点都还在内存当中, 等待commitRoot阶段进行渲染.

    github地址paiDaXing-web (MXM553341082) · GitHub

    求start

  • 相关阅读:
    实现动态表单的一种思路
    Mybatis-Plus知识点总结(下)
    QT画图板
    HTTP 请求中的请求方法有哪些常见的类型?
    【vue】element强制刷新el-carousel的dom:
    【摸鱼系列】如何用Python做一个有趣的Loading彩蛋游戏~
    微信小程序-上拉加载和下拉刷新
    redis基础操作
    开发自己的Prometheus Exporter、实现自定义指标
    用Rust手把手编写一个Proxy(代理), 动工
  • 原文地址:https://blog.csdn.net/weixin_44828588/article/details/126500630