• 【React源码】(十)fiber 树构造(对比更新)


    fiber 树构造(对比更新)

    在前文fiber 树构造(初次创建)一文的介绍中, 演示了fiber树构造循环中逐步构造fiber树的过程. 由于是初次创建, 所以在构造过程中, 所有节点都是新建, 并没有复用旧节点.

    本节讨论对比更新这种情况(在Legacy模式下进行分析). 在阅读本节之前, 最好对fiber 树构造(初次创建)有一些了解, 其中有很多相似逻辑不再重复叙述, 本节重点突出对比更新初次创建的不同之处.

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

     
    
    1. import React from 'react';
    2. class App extends React.Component {
    3. state = {
    4. list: ['A', 'B', 'C'],
    5. };
    6. onChange = () => {
    7. this.setState({ list: ['C', 'A', 'X'] });
    8. };
    9. componentDidMount() {
    10. console.log(`App Mount`);
    11. }
    12. render() {
    13. return (
    14. <>
    15. <Header />
    16. <button onClick={this.onChange}>changebutton>
    17. <div className="content">
    18. {this.state.list.map(item => (
    19. <p key={item}>{item}p>
    20. ))}
    21. div>
    22. );
    23. }
    24. }
    25. class Header extends React.PureComponent {
    26. render() {
    27. return (
    28. <>
    29. <h1>titleh1>
    30. <h2>title2h2>
    31. );
    32. }
    33. }
    34. export default App;

    初次渲染完成之后, 与fiber树相关的内存结构如下(后文以此图为基础, 演示对比更新过程):

    更新入口

    前文reconciler 运作流程中总结的 4 个阶段(从输入到输出), 其中承接输入的函数只有scheduleUpdateOnFiber(源码地址).在react-reconciler对外暴露的 api 函数中, 只要涉及到需要改变 fiber 的操作(无论是首次渲染对比更新), 最后都会间接调用scheduleUpdateOnFiberscheduleUpdateOnFiber函数是输入链路中的必经之路.

    3 种更新方式

    如要主动发起更新, 有 3 种常见方式:

    1. Class组件中调用setState.
    2. Function组件中调用hook对象暴露出的dispatchAction.
    3. container节点上重复调用render(官网示例)

    下面列出这 3 种更新方式的源码:

    setState

    Component对象的原型上挂载有setState(源码链接):

     
    

    1. Component.prototype.setState = function(partialState, callback) {
    2. this.updater.enqueueSetState(this, partialState, callback, 'setState');
    3. };
    4. 在fiber 树构造(初次创建)中的beginWork阶段, class 类型的组件初始化完成之后, this.updater对象如下(源码链接):
    5. const classComponentUpdater = {
    6. isMounted,
    7. enqueueSetState(inst, payload, callback) {
    8. // 1. 获取class实例对应的fiber节点
    9. const fiber = getInstance(inst);
    10. // 2. 创建update对象
    11. const eventTime = requestEventTime();
    12. const lane = requestUpdateLane(fiber); // 确定当前update对象的优先级
    13. const update = createUpdate(eventTime, lane);
    14. update.payload = payload;
    15. if (callback !== undefined && callback !== null) {
    16. update.callback = callback;
    17. }
    18. // 3. 将update对象添加到当前Fiber节点的updateQueue队列当中
    19. enqueueUpdate(fiber, update);
    20. // 4. 进入reconciler运作流程中的`输入`环节
    21. scheduleUpdateOnFiber(fiber, lane, eventTime); // 传入的lane是update优先级
    22. },
    23. };
    24. dispatchAction
    25. Component.prototype.setState = function(partialState, callback) {
    26. this.updater.enqueueSetState(this, partialState, callback, 'setState');
    27. };
    28. 在fiber 树构造(初次创建)中的beginWork阶段, class 类型的组件初始化完成之后, this.updater对象如下(源码链接):
    29. const classComponentUpdater = {
    30. isMounted,
    31. enqueueSetState(inst, payload, callback) {
    32. // 1. 获取class实例对应的fiber节点
    33. const fiber = getInstance(inst);
    34. // 2. 创建update对象
    35. const eventTime = requestEventTime();
    36. const lane = requestUpdateLane(fiber); // 确定当前update对象的优先级
    37. const update = createUpdate(eventTime, lane);
    38. update.payload = payload;
    39. if (callback !== undefined && callback !== null) {
    40. update.callback = callback;
    41. }
    42. // 3. 将update对象添加到当前Fiber节点的updateQueue队列当中
    43. enqueueUpdate(fiber, update);
    44. // 4. 进入reconciler运作流程中的`输入`环节
    45. scheduleUpdateOnFiber(fiber, lane, eventTime); // 传入的lane是update优先级
    46. },
    47. };
    48. dispatchAction

     

    此处只是为了对比dispatchActionsetState. 有关hook原理的深入分析, 在hook 原理章节中详细讨论.

    function类型组件中, 如果使用hook(useState), 则可以通过hook api暴露出的dispatchAction(源码链接)来更新

     
    
    1. function dispatchAction(
    2. fiber: Fiber,
    3. queue: UpdateQueue,
    4. action: A,
    5. ) {
    6. // 1. 创建update对象
    7. const eventTime = requestEventTime();
    8. const lane = requestUpdateLane(fiber); // 确定当前update对象的优先级
    9. const update: Update = {
    10. lane,
    11. action,
    12. eagerReducer: null,
    13. eagerState: null,
    14. next: (null: any),
    15. };
    16. // 2. 将update对象添加到当前Hook对象的updateQueue队列当中
    17. const pending = queue.pending;
    18. if (pending === null) {
    19. update.next = update;
    20. } else {
    21. update.next = pending.next;
    22. pending.next = update;
    23. }
    24. queue.pending = update;
    25. // 3. 请求调度, 进入reconciler运作流程中的`输入`环节.
    26. scheduleUpdateOnFiber(fiber, lane, eventTime); // 传入的lane是update优先级
    27. }
    28. 重复调用 render
    29. import ReactDOM from 'react-dom';
    30. function tick() {
    31. const element = (
    32. <div>
    33. <h1>Hello, world!h1>
    34. <h2>It is {new Date().toLocaleTimeString()}.h2>
    35. div>
    36. );
    37. ReactDOM.render(element, document.getElementById('root'));
    38. }
    39. setInterval(tick, 1000);
    40. 对于重复render, 在React 应用的启动过程中已有说明, 调用路径包含updateContainer-->scheduleUpdateOnFiber
    41. 构造阶段
    42. 逻辑来到scheduleUpdateOnFiber函数:
    43. // ...省略部分代码
    44. export function scheduleUpdateOnFiber(
    45. fiber: Fiber, // fiber表示被更新的节点
    46. lane: Lane, // lane表示update优先级
    47. eventTime: number,
    48. ) {
    49. const root = markUpdateLaneFromFiberToRoot(fiber, lane);
    50. if (lane === SyncLane) {
    51. if (
    52. (executionContext & LegacyUnbatchedContext) !== NoContext &&
    53. (executionContext & (RenderContext | CommitContext)) === NoContext
    54. ) {
    55. // 初次渲染
    56. performSyncWorkOnRoot(root);
    57. } else {
    58. // 对比更新
    59. ensureRootIsScheduled(root, eventTime);
    60. }
    61. }
    62. mostRecentlyUpdatedRoot = root;
    63. }

    对比更新初次渲染的不同点:

    1. markUpdateLaneFromFiberToRoot函数, 只在对比更新阶段才发挥出它的作用, 它找出了fiber树中受到本次update影响的所有节点, 并设置这些节点的fiber.lanesfiber.childLanes(在legacy模式下为SyncLane)以备fiber树构造阶段使用.
     
    
    1. function markUpdateLaneFromFiberToRoot(
    2. sourceFiber: Fiber, // sourceFiber表示被更新的节点
    3. lane: Lane, // lane表示update优先级
    4. ): FiberRoot | null {
    5. // 1. 将update优先级设置到sourceFiber.lanes
    6. sourceFiber.lanes = mergeLanes(sourceFiber.lanes, lane);
    7. let alternate = sourceFiber.alternate;
    8. if (alternate !== null) {
    9. // 同时设置sourceFiber.alternate的优先级
    10. alternate.lanes = mergeLanes(alternate.lanes, lane);
    11. }
    12. // 2. 从sourceFiber开始, 向上遍历所有节点, 直到HostRoot. 设置沿途所有节点(包括alternate)的childLanes
    13. let node = sourceFiber;
    14. let parent = sourceFiber.return;
    15. while (parent !== null) {
    16. parent.childLanes = mergeLanes(parent.childLanes, lane);
    17. alternate = parent.alternate;
    18. if (alternate !== null) {
    19. alternate.childLanes = mergeLanes(alternate.childLanes, lane);
    20. }
    21. node = parent;
    22. parent = parent.return;
    23. }
    24. if (node.tag === HostRoot) {
    25. const root: FiberRoot = node.stateNode;
    26. return root;
    27. } else {
    28. return null;
    29. }
    30. }

    markUpdateLaneFromFiberToRoot

    下图表示了markUpdateLaneFromFiberToRoot的具体作用:

    • sourceFiber为起点, 设置起点的fiber.lanes
    • 从起点开始, 直到HostRootFiber, 设置父路径上所有节点(也包括fiber.alternate)的fiber.childLanes.
    • 通过设置fiber.lanesfiber.childLanes就可以辅助判断子树是否需要更新(在下文循环构造中详细说明).

    1. 对比更新没有直接调用performSyncWorkOnRoot, 而是通过调度中心来处理, 由于本示例是在Legacy模式下进行, 最后会同步执行performSyncWorkOnRoot.(详细原理可以参考React 调度原理(scheduler)). 所以其调用链路performSyncWorkOnRoot--->renderRootSync--->workLoopSync初次构造中的一致.

    renderRootSync中:

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

    进入循环构造(workLoopSync)前, 会刷新栈帧(调用prepareFreshStack)(参考fiber 树构造(基础准备)栈帧管理).

    此时的内存结构如下:

    注意:

    • fiberRoot.current指向与当前页面对应的fiber树workInProgress指向正在构造的fiber树.
    • 刷新栈帧会调用createWorkInProgress(), 使得workInProgress.flags和workInProgress.effects都已经被重置. 且workInProgress.child = current.child. 所以在进入循环构造之前, HostRootFiberHostRootFiber.alternate共用一个child(这里是fiber()).

    循环构造

    回顾一下fiber 树构造(初次创建)中的介绍. 整个fiber树构造是一个深度优先遍历(可参考React 算法之深度优先遍历), 其中有 2 个重要的变量workInProgresscurrent(可参考fiber 树构造(基础准备)中介绍的双缓冲技术):

    • workInProgresscurrent都视为指针
    • workInProgress指向当前正在构造的fiber节点
    • current = workInProgress.alternate(即fiber.alternate), 指向当前页面正在使用的fiber节点.

    在深度优先遍历中, 每个fiber节点都会经历 2 个阶段:

    1. 探寻阶段 beginWork
    2. 回溯阶段 completeWork

    这 2 个阶段共同完成了每一个fiber节点的创建(或更新), 所有fiber节点则构成了fiber树.

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

    注意: 在对比更新过程中current = unitOfWork.alternate;不为null, 后续的调用逻辑中会大量使用此处传入的current.

    探寻阶段 beginWork

    beginWork(current, unitOfWork, subtreeRenderLanes)(源码地址).

     
    
    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. // 进入对比
    9. const oldProps = current.memoizedProps;
    10. const newProps = workInProgress.pendingProps;
    11. if (
    12. oldProps !== newProps ||
    13. hasLegacyContextChanged() ||
    14. (__DEV__ ? workInProgress.type !== current.type : false)
    15. ) {
    16. didReceiveUpdate = true;
    17. } else if (!includesSomeLane(renderLanes, updateLanes)) {
    18. // 当前渲染优先级renderLanes不包括fiber.lanes, 表明当前fiber节点无需更新
    19. didReceiveUpdate = false;
    20. switch (
    21. workInProgress.tag
    22. // switch 语句中包括 context相关逻辑, 本节暂不讨论(不影响分析fiber树构造)
    23. ) {
    24. }
    25. // 当前fiber节点无需更新, 调用bailoutOnAlreadyFinishedWork循环检测子节点是否需要更新
    26. return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
    27. }
    28. }
    29. // 余下逻辑与初次创建共用
    30. // 1. 设置workInProgress优先级为NoLanes(最高优先级)
    31. workInProgress.lanes = NoLanes;
    32. // 2. 根据workInProgress节点的类型, 用不同的方法派生出子节点
    33. switch (
    34. workInProgress.tag // 只列出部分case
    35. ) {
    36. case ClassComponent: {
    37. const Component = workInProgress.type;
    38. const unresolvedProps = workInProgress.pendingProps;
    39. const resolvedProps =
    40. workInProgress.elementType === Component
    41. ? unresolvedProps
    42. : resolveDefaultProps(Component, unresolvedProps);
    43. return updateClassComponent(
    44. current,
    45. workInProgress,
    46. Component,
    47. resolvedProps,
    48. renderLanes,
    49. );
    50. }
    51. case HostRoot:
    52. return updateHostRoot(current, workInProgress, renderLanes);
    53. case HostComponent:
    54. return updateHostComponent(current, workInProgress, renderLanes);
    55. case HostText:
    56. return updateHostText(current, workInProgress);
    57. case Fragment:
    58. return updateFragment(current, workInProgress, renderLanes);
    59. }
    60. }

    bailout逻辑 {#bailout}

    bail out英文短语翻译为解救, 纾困, 在源码中, bailout用于判断子树节点是否完全复用, 如果可以复用, 则会略过 fiber 树构造.

    初次创建不同, 在对比更新过程中, 如果是老节点, 那么current !== null, 需要进行对比, 然后决定是否复用老节点及其子树(即bailout逻辑).

    1. !includesSomeLane(renderLanes, updateLanes)这个判断分支, 包含了渲染优先级update优先级的比较(详情可以回顾fiber 树构造(基础准备)优先级相关解读), 如果当前节点无需更新, 则会进入bailout逻辑.
    2. 最后会调用bailoutOnAlreadyFinishedWork:
      • 如果同时满足!includesSomeLane(renderLanes, workInProgress.childLanes), 表明该 fiber 节点及其子树都无需更新, 可直接进入回溯阶段(completeUnitOfWork)
      • 如果不满足!includesSomeLane(renderLanes, workInProgress.childLanes), 意味着子节点需要更新, clone并返回子节点.
     
    
    1. // 省略部分无关代码
    2. function bailoutOnAlreadyFinishedWork(
    3. current: Fiber | null,
    4. workInProgress: Fiber,
    5. renderLanes: Lanes,
    6. ): Fiber | null {
    7. if (!includesSomeLane(renderLanes, workInProgress.childLanes)) {
    8. // 渲染优先级不包括 workInProgress.childLanes, 表明子节点也无需更新. 返回null, 直接进入回溯阶段.
    9. return null;
    10. } else {
    11. // 本fiber虽然不用更新, 但是子节点需要更新. clone并返回子节点
    12. cloneChildFibers(current, workInProgress);
    13. return workInProgress.child;
    14. }
    15. }

    注意: cloneChildFibers内部调用createWorkInProgress, 在构造fiber节点时会优先复用workInProgress.alternate(不开辟新的内存空间), 否则才会创建新的fiber对象.

    updateXXX函数

    updateXXX函数(如: updateHostRoot, updateClassComponent 等)的主干逻辑与初次构造过程完全一致, 总的目的是为了向下生成子节点, 并在这个过程中调用reconcileChildren调和函数, 只要fiber节点有副作用, 就会把特殊操作设置到fiber.flags(如:节点ref,class组件的生命周期,function组件的hook,节点删除等).

    对比更新过程的不同之处:

    1. bailoutOnAlreadyFinishedWork
      • 对比更新时如果遇到当前节点无需更新(如: class类型的节点且shouldComponentUpdate返回false), 会再次进入bailout逻辑.
    2. reconcileChildren调和函数
      • 调和函数是updateXXX函数中的一项重要逻辑, 它的作用是向下生成子节点, 并设置fiber.flags.
      • 初次创建fiber节点没有比较对象, 所以在向下生成子节点的时候没有任何多余的逻辑, 只管创建就行.
      • 对比更新时需要把ReactElement对象与旧fiber对象进行比较, 来判断是否需要复用旧fiber对象.

    注: 本节的重点是fiber树构造, 在对比更新过程中reconcileChildren()函数实现的diff算法十分重要, 但是它只是处于算法层面, 对于diff算法的实现,在React 算法之调和算法中单独分析.

    本节只需要先了解调和函数目的:

    1. 给新增,移动,和删除节点设置fiber.flags(新增,移动: Placement, 删除: Deletion)
    2. 如果是需要删除的fiber除了自身打上Deletion之外, 还要将其添加到父节点的effects链表中(正常副作用队列的处理是在completeWork函数, 但是该节点(被删除)会脱离fiber树, 不会再进入completeWork阶段, 所以在beginWork阶段提前加入副作用队列).

    回溯阶段 completeWork

    completeUnitOfWork(unitOfWork)函数(源码地址)在初次创建对比更新逻辑一致, 都是处理beginWork 阶段已经创建出来的 fiber 节点, 最后创建(更新)DOM 对象, 并上移副作用队列.

    在这里我们重点关注completeWork函数中, current !== null的情况:

     
    
    1. // ...省略无关代码
    2. function completeWork(
    3. current: Fiber | null,
    4. workInProgress: Fiber,
    5. renderLanes: Lanes,
    6. ): Fiber | null {
    7. const newProps = workInProgress.pendingProps;
    8. switch (workInProgress.tag) {
    9. case HostComponent: {
    10. // 非文本节点
    11. popHostContext(workInProgress);
    12. const rootContainerInstance = getRootHostContainer();
    13. const type = workInProgress.type;
    14. if (current !== null && workInProgress.stateNode != null) {
    15. // 处理改动
    16. updateHostComponent(
    17. current,
    18. workInProgress,
    19. type,
    20. newProps,
    21. rootContainerInstance,
    22. );
    23. if (current.ref !== workInProgress.ref) {
    24. markRef(workInProgress);
    25. }
    26. } else {
    27. // ...省略无关代码
    28. }
    29. return null;
    30. }
    31. case HostText: {
    32. // 文本节点
    33. const newText = newProps;
    34. if (current && workInProgress.stateNode != null) {
    35. const oldText = current.memoizedProps;
    36. // 处理改动
    37. updateHostText(current, workInProgress, oldText, newText);
    38. } else {
    39. // ...省略无关代码
    40. }
    41. return null;
    42. }
    43. }
    44. }
    45. updateHostComponent = function(
    46. current: Fiber,
    47. workInProgress: Fiber,
    48. type: Type,
    49. newProps: Props,
    50. rootContainerInstance: Container,
    51. ) {
    52. const oldProps = current.memoizedProps;
    53. if (oldProps === newProps) {
    54. return;
    55. }
    56. const instance: Instance = workInProgress.stateNode;
    57. const currentHostContext = getHostContext();
    58. const updatePayload = prepareUpdate(
    59. instance,
    60. type,
    61. oldProps,
    62. newProps,
    63. rootContainerInstance,
    64. currentHostContext,
    65. );
    66. workInProgress.updateQueue = (updatePayload: any);
    67. // 如果有属性变动, 设置fiber.flags |= Update, 等待`commit`阶段的处理
    68. if (updatePayload) {
    69. markUpdate(workInProgress);
    70. }
    71. };
    72. updateHostText = function(
    73. current: Fiber,
    74. workInProgress: Fiber,
    75. oldText: string,
    76. newText: string,
    77. ) {
    78. // 如果有属性变动, 设置fiber.flags |= Update, 等待`commit`阶段的处理
    79. if (oldText !== newText) {
    80. markUpdate(workInProgress);
    81. }
    82. };

    可以看到在更新过程中, 如果 DOM 属性有变化, 不会再次新建 DOM 对象, 而是设置fiber.flags |= Update, 等待commit阶段处理(源码链接).

    过程图解

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

    构造前:

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

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

    • 执行前: workInProgress指向HostRootFiber.alternate对象, 此时current = workInProgress.alternate指向当前页面对应的fiber树.
    • 执行过程:
      • 因为current !== null且当前节点fiber.lanes不在渲染优先级范围内, 故进入bailoutOnAlreadyFinishedWork逻辑
      • 又因为fiber.childLanes处于渲染优先级范围内, 证明child节点需要更新, 克隆workInProgress.child节点.
      • clone之后, 新fiber节点会丢弃旧fiber上的标志位(flags)和副作用(effects), 其他属性会继续保留.
    • 执行后: 返回被clone的下级节点fiber(), 移动workInProgress指向子节点fiber()

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

    • 执行前: workInProgress指向fiber()节点, 且current = workInProgress.alternate有值
    • 执行过程:
      • 当前节点fiber.lanes处于渲染优先级范围内, 会进入updateClassComponent()函数
      • updateClassComponent()函数中, 调用reconcileChildren()生成下级子节点.
    • 执行后: 返回下级节点fiber(
      ), 移动workInProgress指向子节点fiber(
      )

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

    • beginWork执行前: workInProgress指向fiber(
      ), 且current = workInProgress.alternate有值
    • beginWork执行过程:
      • 当前节点fiber.lanes处于渲染优先级范围内, 会进入updateClassComponent()函数
      • updateClassComponent()函数中, 由于此组件是PureComponentshouldComponentUpdate判定为false,故进入bailoutOnAlreadyFinishedWork逻辑.
      • 又因为fiber.childLanes不在渲染优先级范围内, 证明child节点也不需要更新
    • beginWork执行后: 因为完全满足bailout逻辑, 返回null. 所以进入completeUnitOfWork(unitOfWork)函数, 传入的参数unitOfWork实际上就是workInProgress(此时指向fiber(
      ))

     

    • completeUnitOfWork执行前: workInProgress指向fiber(
      )
    • completeUnitOfWork执行过程: 以fiber(
      )为起点, 向上回溯

    completeUnitOfWork第 1 次循环:

    1. 执行completeWork函数: class类型的组件无需处理.
    2. 上移副作用队列: 由于本节点fiber(header)没有副作用(fiber.flags = 0), 所以执行之后副作用队列没有实质变化(目前为空).
    3. 向上回溯: 由于还有兄弟节点, 把workInProgress指向下一个兄弟节点fiber(button), 退出completeUnitOfWork.

     

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

    • beginWork执行过程: 调用updateHostComponent

      • 本示例中button的子节点是一个直接文本节点,设置nextChildren = null(源码注释的解释是不用在开辟内存去创建一个文本节点, 同时还能减少向下遍历).
      • 由于nextChildren = null, 经过reconcileChildren阶段处理后, 返回值也是null
    • beginWork执行后: 由于下级节点为null, 所以进入completeUnitOfWork(unitOfWork)函数, 传入的参数unitOfWork实际上就是workInProgress(此时指向fiber(button)节点)

    • completeUnitOfWork执行过程: 以fiber(button)为起点, 向上回溯

    completeUnitOfWork第 1 次循环:

    1. 执行completeWork函数
      • 因为fiber(button).stateNode != null, 所以无需再次创建 DOM 对象. 只需要进一步调用updateHostComponent()记录 DOM 属性改动情况
      • updateHostComponent()函数中, 又因为oldProps === newProps, 所以无需记录改动情况, 直接返回
    2. 上移副作用队列: 由于本节点fiber(button)没有副作用(fiber.flags = 0), 所以执行之后副作用队列没有实质变化(目前为空).
    3. 向上回溯: 由于还有兄弟节点, 把workInProgress指向下一个兄弟节点fiber(div), 退出completeUnitOfWork.

    performUnitOfWork第 5 次调用(执行beginWork):

    • 执行前: workInProgress指向fiber(div)节点, 且current = workInProgress.alternate有值
    • 执行过程:
      • updateHostComponent()函数中, 调用reconcileChildren()生成下级子节点.
      • 需要注意的是, 下级子节点是一个可迭代数组, 会把fiber.child.sibling一起构造出来, 同时根据需要设置fiber.flags. 在本例中, 下级节点有被删除的情况, 被删除的节点会被添加到父节点的副作用队列中(具体实现方式请参考React 算法之调和算法).
    • 执行后: 返回下级节点fiber(p), 移动workInProgress指向子节点fiber(p)

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

    • beginWork执行过程: 与第 4 次调用中构建fiber(button)的逻辑完全一致, 因为都是直接文本节点, reconcileChildren()返回的下级子节点为 null.

    • beginWork执行后: 由于下级节点为null, 所以进入completeUnitOfWork(unitOfWork)函数

    • completeUnitOfWork执行过程: 以fiber(p)为起点, 向上回溯

    completeUnitOfWork第 1 次循环:

    1. 执行completeWork函数
      • 因为fiber(p).stateNode != null, 所以无需再次创建 DOM 对象. 在updateHostComponent()函数中, 又因为节点属性没有变动, 所以无需打标记
    2. 上移副作用队列: 本节点fiber(p)没有副作用(fiber.flags = 0).
    3. 向上回溯: 由于还有兄弟节点, 把workInProgress指向下一个兄弟节点fiber(p), 退出completeUnitOfWork.

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

    • beginWork执行过程: 与第 4 次调用中构建fiber(button)的逻辑完全一致, 因为都是直接文本节点, reconcileChildren()返回的下级子节点为 null.

    • beginWork执行后: 由于下级节点为null, 所以进入completeUnitOfWork(unitOfWork)函数

    • completeUnitOfWork执行过程: 以fiber(p)为起点, 向上回溯

    completeUnitOfWork第 1 次循环:

    1. 执行completeWork函数:

      • 因为fiber(p).stateNode != null, 所以无需再次创建 DOM 对象. 在updateHostComponent()函数中, 又因为节点属性没有变动, 所以无需打标记
    2. 上移副作用队列: 本节点fiber(p)有副作用(fiber.flags = Placement), 需要将其添加到父节点的副作用队列之后.

    3. 向上回溯: 由于还有兄弟节点, 把workInProgress指向下一个兄弟节点fiber(p), 退出completeUnitOfWork.

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

    • beginWork执行过程: 本节点fiber(p)是一个新增节点, 其current === null, 会进入updateHostComponent()函数. 因为是直接文本节点, reconcileChildren()返回的下级子节点为 null.

    • beginWork执行后: 由于下级节点为null, 所以进入completeUnitOfWork(unitOfWork)函数

    • completeUnitOfWork执行过程: 以fiber(p)为起点, 向上回溯

    completeUnitOfWork第 1 次循环:

    1. 执行completeWork函数: 由于本节点是一个新增节点,且fiber(p).stateNode === null, 所以创建fiber(p)节点对应的DOM实例, 挂载到fiber.stateNode之上.
    2. 上移副作用队列: 本节点fiber(p)有副作用(fiber.flags = Placement), 需要将其添加到父节点的副作用队列之后.
    3. 向上回溯: 由于没有兄弟节点, 把workInProgress指针指向父节点fiber(div).

    completeUnitOfWork第 2 次循环:

    1. 执行completeWork函数: 由于div组件没有属性变动, 故updateHostComponent()没有设置副作用标记
    2. 上移副作用队列: 本节点fiber(div)的副作用队列添加到父节点的副作用队列之后.
    3. 向上回溯: 由于没有兄弟节点, 把workInProgress指针指向父节点fiber()

    completeUnitOfWork第 3 次循环:

    1. 执行completeWork函数: class 类型的节点无需处理
    2. 上移副作用队列: 本节点fiber()的副作用队列添加到父节点的副作用队列之后.
    3. 向上回溯: 由于没有兄弟节点, 把workInProgress指针指向父节点fiber(HostRootFiber)

    completeUnitOfWork第 4 次循环:

    1. 执行completeWork函数: HostRoot类型的节点无需处理
    2. 向上回溯: 由于父节点为空, 无需进入处理副作用队列的逻辑. 最后设置workInProgress=null, 并退出completeUnitOfWork
    3. 重置fiber.childLanes

    到此整个fiber树构造循环(对比更新)已经执行完毕, 拥有一棵新的fiber树, 并且在fiber树的根节点上挂载了副作用队列. renderRootSync函数退出之前, 会重置workInProgressRoot = null, 表明没有正在进行中的render. 且把最新的fiber树挂载到fiberRoot.finishedWork上. 这时整个 fiber 树的内存结构如下(注意fiberRoot.finishedWorkfiberRoot.current指针,在commitRoot阶段会进行处理):

    无论是初次构造或者是对比更新, 当fiber树构造完成之后, 余下的逻辑几乎一致, 在fiber 树渲染中继续讨论.

    总结

    本节演示了更新阶段fiber树构造(对比更新)的全部过程, 跟踪了创建过程中内存引用的变化情况. 与初次构造最大的不同在于fiber节点是否可以复用, 其中bailout逻辑是fiber子树能否复用的判断依据.

  • 相关阅读:
    MySQL查询进阶——从函数到表连接的使用你还记得吗
    ABB机器人欧拉角与四元数的相互转化以及旋转矩阵的求法
    ATV71驱动同步电机调试技巧
    Docker Compose命令讲解+文件编写
    (操作系统开发)从实模式---->保护模式---->IA-32e模式( 64位模式)
    SpringBoot 40 个常用注解:让生产力爆表!(荣耀典藏版)
    超低延时4K级可定制化专业视觉计算平台
    Java开发学习(二十五)----使用PostMan完成不同类型参数传递
    电路布线问题动态规划详解(做题思路)
    Spring的ioc的扫描器与bean的作用域与生命周期
  • 原文地址:https://blog.csdn.net/weixin_44828588/article/details/126501047