上一篇可以了解到,组件执行beginWork后会创建子Fiber节点,节点上可能存在Effects List

方法调用栈如下: performUnitOfWork —> beginWork—> updateClassComponent —> finishedComponent —> completeUnitOfWork

performUnitOfWork方法中,如果遍历完创建了所有的Fiber节点,就进行completeUnitOfWork方法,创建对应的DOM节点。

  1. if (next === null) {
  2. // If this doesn't spawn new work, complete the current work.
  3. completeUnitOfWork(unitOfWork);
  4. }

接下来看一下 completeUnitOfWork方法

  1. function completeUnitOfWork(unitOfWork: Fiber): void {
  2. //准备完成当前Fiber节点的工作,完成之后就准备完成下一个兄弟Fiber节点的工作,如果没有兄弟Fiber节点,那就回到父Fiber节点继续工作。
  3. let completedWork = unitOfWork;
  4. do {
  5. const current = completedWork.alternate;
  6. const returnFiber = completedWork.return;
  7. // Check if the work completed or if something threw.
  8. if ((completedWork.flags & Incomplete) === NoFlags) {
  9. setCurrentDebugFiberInDEV(completedWork);
  10. let next;
  11. if (
  12. !enableProfilerTimer ||
  13. (completedWork.mode & ProfileMode) === NoMode
  14. ) {
  15. next = completeWork(current, completedWork, subtreeRenderLanes);
  16. } else {
  17. startProfilerTimer(completedWork);
  18. next = completeWork(current, completedWork, subtreeRenderLanes);
  19. // Update render duration assuming we didn't error.
  20. stopProfilerTimerIfRunningAndRecordDelta(completedWork, false);
  21. }
  22. resetCurrentDebugFiberInDEV();
  23. if (next !== null) {
  24. // Completing this fiber spawned new work. Work on that next.
  25. workInProgress = next;
  26. return;
  27. }
  28. resetChildLanes(completedWork);
  29. if (
  30. returnFiber !== null &&
  31. // Do not append effects to parents if a sibling failed to complete
  32. (returnFiber.flags & Incomplete) === NoFlags
  33. ) {
  34. // Append all the effects of the subtree and this fiber onto the effect
  35. // list of the parent. The completion order of the children affects the
  36. // side-effect order.
  37. if (returnFiber.firstEffect === null) {
  38. returnFiber.firstEffect = completedWork.firstEffect;
  39. }
  40. if (completedWork.lastEffect !== null) {
  41. if (returnFiber.lastEffect !== null) {
  42. returnFiber.lastEffect.nextEffect = completedWork.firstEffect;
  43. }
  44. returnFiber.lastEffect = completedWork.lastEffect;
  45. }
  46. const flags = completedWork.flags;
  47. // Skip both NoWork and PerformedWork tags when creating the effect
  48. // list. PerformedWork effect is read by React DevTools but shouldn't be
  49. // committed.
  50. if (flags > PerformedWork) {
  51. if (returnFiber.lastEffect !== null) {
  52. returnFiber.lastEffect.nextEffect = completedWork;
  53. } else {
  54. returnFiber.firstEffect = completedWork;
  55. }
  56. returnFiber.lastEffect = completedWork;
  57. }
  58. }
  59. } else {
  60. // This fiber did not complete because something threw. Pop values off
  61. // the stack without entering the complete phase. If this is a boundary,
  62. // capture values if possible.
  63. const next = unwindWork(completedWork, subtreeRenderLanes);
  64. // Because this fiber did not complete, don't reset its expiration time.
  65. if (next !== null) {
  66. // If completing this work spawned new work, do that next. We'll come
  67. // back here again.
  68. // Since we're restarting, remove anything that is not a host effect
  69. // from the effect tag.
  70. next.flags &= HostEffectMask;
  71. workInProgress = next;
  72. return;
  73. }
  74. if (
  75. enableProfilerTimer &&
  76. (completedWork.mode & ProfileMode) !== NoMode
  77. ) {
  78. // Record the render duration for the fiber that errored.
  79. stopProfilerTimerIfRunningAndRecordDelta(completedWork, false);
  80. // Include the time spent working on failed children before continuing.
  81. let actualDuration = completedWork.actualDuration;
  82. let child = completedWork.child;
  83. while (child !== null) {
  84. actualDuration += child.actualDuration;
  85. child = child.sibling;
  86. }
  87. completedWork.actualDuration = actualDuration;
  88. }
  89. if (returnFiber !== null) {
  90. // Mark the parent fiber as incomplete and clear its subtree flags.
  91. returnFiber.flags |= Incomplete;
  92. returnFiber.subtreeFlags = NoFlags;
  93. returnFiber.deletions = null;
  94. }
  95. }
  96. const siblingFiber = completedWork.sibling;
  97. if (siblingFiber !== null) {
  98. // If there is more work to do in this returnFiber, do that next.
  99. workInProgress = siblingFiber;
  100. return;
  101. }
  102. // Otherwise, return to the parent
  103. completedWork = returnFiber;
  104. // Update the next thing we're working on in case something throws.
  105. workInProgress = completedWork;
  106. } while (completedWork !== null);
  107. // We've reached the root.
  108. if (workInProgressRootExitStatus === RootIncomplete) {
  109. workInProgressRootExitStatus = RootCompleted;
  110. }
  111. }

completeWork

  1. function completeWork(
  2. current: Fiber | null,
  3. workInProgress: Fiber,
  4. renderLanes: Lanes,
  5. ): Fiber | null {
  6. const newProps = workInProgress.pendingProps;
  7. switch (workInProgress.tag) {
  8. case IndeterminateComponent:
  9. case LazyComponent:
  10. case SimpleMemoComponent:
  11. case FunctionComponent:
  12. case ForwardRef:
  13. case Fragment:
  14. case Mode:
  15. case Profiler:
  16. case ContextConsumer:
  17. case MemoComponent:
  18. bubbleProperties(workInProgress);
  19. return null;
  20. case ClassComponent: {
  21. const Component = workInProgress.type;
  22. if (isLegacyContextProvider(Component)) {
  23. popLegacyContext(workInProgress);
  24. }
  25. bubbleProperties(workInProgress);
  26. return null;
  27. }
  28. case HostRoot: {
  29. const fiberRoot = (workInProgress.stateNode: FiberRoot);
  30. //...
  31. updateHostContainer(current, workInProgress);
  32. bubbleProperties(workInProgress);
  33. return null;
  34. }
  35. case HostComponent: {//...}
  36. case HostText: {//...}
  37. case SuspenseComponent: {//...}

这里先讲解页面渲染所必须的HostComponent(即原生DOM组件对应的Fiber节点

beginWork一样,根据current === null来 判断是mount还是update

  1. case HostComponent: {
  2. popHostContext(workInProgress);
  3. const rootContainerInstance = getRootHostContainer();
  4. const type = workInProgress.type;
  5. if (current !== null && workInProgress.stateNode != null) {
  6. updateHostComponent(
  7. current,
  8. workInProgress,
  9. type,
  10. newProps,
  11. rootContainerInstance,
  12. );
  13. if (current.ref !== workInProgress.ref) {
  14. markRef(workInProgress);
  15. }
  16. } else {
  17. if (!newProps) {
  18. invariant(
  19. workInProgress.stateNode !== null,
  20. 'We must have new props for new mounts. This error is likely ' +
  21. 'caused by a bug in React. Please file an issue.',
  22. );
  23. // This can happen when we abort work.
  24. bubbleProperties(workInProgress);
  25. return null;
  26. }
  27. const currentHostContext = getHostContext();
  28. // TODO: Move createInstance to beginWork and keep it on a context
  29. // "stack" as the parent. Then append children as we go in beginWork
  30. // or completeWork depending on whether we want to add them top->down or
  31. // bottom->up. Top->down is faster in IE11.
  32. const wasHydrated = popHydrationState(workInProgress);
  33. if (wasHydrated) {
  34. // TODO: Move this and createInstance step into the beginPhase
  35. // to consolidate.
  36. if (
  37. prepareToHydrateHostInstance(
  38. workInProgress,
  39. rootContainerInstance,
  40. currentHostContext,
  41. )
  42. ) {
  43. // If changes to the hydrated node need to be applied at the
  44. // commit-phase we mark this as such.
  45. markUpdate(workInProgress);
  46. }
  47. } else {
  48. const instance = createInstance(
  49. type,
  50. newProps,
  51. rootContainerInstance,
  52. currentHostContext,
  53. workInProgress,
  54. );
  55. appendAllChildren(instance, workInProgress, false, false);
  56. workInProgress.stateNode = instance;
  57. if (
  58. finalizeInitialChildren(
  59. instance,
  60. type,
  61. newProps,
  62. rootContainerInstance,
  63. currentHostContext,
  64. )
  65. ) {
  66. markUpdate(workInProgress);
  67. }
  68. }
  69. if (workInProgress.ref !== null) {
  70. // If there is a ref on a host node we need to schedule a callback
  71. markRef(workInProgress);
  72. }
  73. }
  74. bubbleProperties(workInProgress);
  75. return null;

update时

updateHostComponent

  1. updateHostComponent = function(
  2. current: Fiber,
  3. workInProgress: Fiber,
  4. type: Type,
  5. newProps: Props,
  6. rootContainerInstance: Container,
  7. ) {
  8. // If we have an alternate, that means this is an update and we need to
  9. // schedule a side-effect to do the updates.
  10. const oldProps = current.memoizedProps;
  11. if (oldProps === newProps) {
  12. // In mutation mode, this is sufficient for a bailout because
  13. // we won't touch this node even if children changed.
  14. return;
  15. }
  16. // If we get updated because one of our children updated, we don't
  17. // have newProps so we'll have to reuse them.
  18. // TODO: Split the update API as separate for the props vs. children.
  19. // Even better would be if children weren't special cased at all tho.
  20. const instance: Instance = workInProgress.stateNode;
  21. const currentHostContext = getHostContext();
  22. // TODO: Experiencing an error where oldProps is null. Suggests a host
  23. // component is hitting the resume path. Figure out why. Possibly
  24. // related to `hidden`.
  25. const updatePayload = prepareUpdate(
  26. instance,
  27. type,
  28. oldProps,
  29. newProps,
  30. rootContainerInstance,
  31. currentHostContext,
  32. );
  33. // TODO: Type this specific to this type of component.
  34. workInProgress.updateQueue = (updatePayload: any);
  35. // If the update payload indicates that there is a change or if there
  36. // is a new ref we mark this as an update. All the work is done in commitWork.
  37. if (updatePayload) {
  38. markUpdate(workInProgress);
  39. }
  40. };

如果是update并且此workInProgress Fiber节点对应的原生DOM节点存在,就不需要生成DOM节点,只需要对props进行处理就行了。这里的props主要是:

  • onClickonChange等回调函数的注册
  • 处理style prop
  • 处理DANGEROUSLY_SET_INNER_HTML prop
  • 处理children prop

被处理完props会被存入workInProgress.updateQueue,并最终会在commit阶段被渲染在页面上。

  1. import React, { useState } from "./react";
  2. import ReactDOM from "./react-dom";
  3. const rootElement = document.getElementById("root");
  4. function App() {
  5. const [count, updateCount] = useState(0);
  6. return (
  7. <h3 title={count} dev={count} onClick={() => updateCount(count + 1)}>
  8. <p>hello word</p>
  9. </h3>
  10. );
  11. }
  12. window.ReactDOM.render(<App />, rootElement);

点击区域,更新count的值,h3的props发生改变,改变内容为updatePayload

其中updatePayload数组形式,他的偶数索引的值为变化的prop key奇数索引的值为变化的prop value

React Reconciler Render阶段之completeWork理解 - 图1

mount时

  1. // 为Fiber创建对应DOM节点
  2. const instance = createInstance(
  3. type,
  4. newProps,
  5. rootContainerInstance,
  6. currentHostContext,
  7. workInProgress,
  8. );
  9. // 将子孙DOM节点插入刚生成的DOM节点中
  10. appendAllChildren(instance, workInProgress, false, false);
  11. // DOM节点赋值给fiber.stateNode
  12. workInProgress.stateNode = instance;
  13. //处理props
  14. if (
  15. finalizeInitialChildren(
  16. instance,
  17. type,
  18. newProps,
  19. rootContainerInstance,
  20. currentHostContext,
  21. )
  22. ) {
  23. markUpdate(workInProgress);
  24. }

completeWork 会从子Fiber节点归到rootFiber节点,每次调用appendAllChildren时都会将已生成的子孙DOM节点插入当前生成的DOM节点下。那么递归到rootFiber时,我们已经有一个构建好的离屏DOM树

构建完DOM树后,render阶段全部工作完成。在performSyncWorkOnRoot函数fiberRootNode被传递给mommitRoot方法,开启commit阶段工作流程。

所以completeWork工作流程如下:

React Reconciler Render阶段之completeWork理解 - 图2