commitWork前,会将在workloopSync中生成的workInProgressfiber树赋值给fiberRootfinishedWork属性。

  1. const finishedWork: Fiber = (root.current.alternate: any);
  2. root.finishedWork = finishedWork;
  3. root.finishedLanes = lanes;

commitRootImpl(root, renderPriorityLevel)的源码

在输出阶段,commitRoot的实现逻辑是在commitRootImpl函数中,其主要逻辑是处理副作用,执行副作用对应的DOM操作,将最新的 fiber 树结构反映到 DOM 上。除此之外,一些生命周期钩子(比如componentDidXXX)、hook(比如useEffect)需要在commit阶段执行。

commit阶段被分成几个子阶段。对每个阶段的副作用列表做了一个单独的处理,也就是通过调用生命周期函数和hooks进行相应的更新。

commit阶段的主要工作(即Renderer的工作流程)分为三部分:

  • before mutation阶段(执行DOM操作前)
  • mutation阶段(执行DOM操作)
  • layout阶段(执行DOM操作后)
  1. function commitRootImpl(root, renderPriorityLevel) {
  2. do {
  3. flushPassiveEffects();
  4. } while (rootWithPendingPassiveEffects !== null);
  5. flushRenderPhaseStrictModeWarningsInDEV();
  6. const finishedWork = root.finishedWork;
  7. const lanes = root.finishedLanes;
  8. if (enableSchedulingProfiler) {
  9. markCommitStarted(lanes);
  10. }
  11. if (finishedWork === null) {
  12. if (enableSchedulingProfiler) {
  13. markCommitStopped();
  14. }
  15. return null;
  16. }
  17. root.finishedWork = null;
  18. root.finishedLanes = NoLanes;
  19. root.callbackNode = null;
  20. let remainingLanes = mergeLanes(finishedWork.lanes, finishedWork.childLanes);
  21. markRootFinished(root, remainingLanes);
  22. //离散事件导致的更新处理=》光标
  23. if (rootsWithPendingDiscreteUpdates !== null) {
  24. if (
  25. !hasDiscreteLanes(remainingLanes) &&
  26. rootsWithPendingDiscreteUpdates.has(root)
  27. ) {
  28. rootsWithPendingDiscreteUpdates.delete(root);
  29. }
  30. }
  31. if (root === workInProgressRoot) {
  32. // We can reset these now that they are finished.
  33. workInProgressRoot = null;
  34. workInProgress = null;
  35. workInProgressRootRenderLanes = NoLanes;
  36. } else {
  37. }
  38. // Get the list of effects.
  39. 在归阶段的update时,effects链表的形成,只会挂载自己的子Fiber,所以当前应用的根节点的Effect是没有被挂载在这个链表上的
  40. let firstEffect;
  41. //如果当前应用的根节点存在Effect,需要将当前应用的根节点挂载在Effect链表的最后
  42. if (finishedWork.flags > PerformedWork) {
  43. if (finishedWork.lastEffect !== null) {
  44. finishedWork.lastEffect.nextEffect = finishedWork;
  45. firstEffect = finishedWork.firstEffect;
  46. } else {
  47. //如果effect list 不存在,第一项就是根Fiber节点
  48. firstEffect = finishedWork;
  49. }
  50. } else {
  51. // There is no effect on the root.
  52. firstEffect = finishedWork.firstEffect;
  53. }
  54. if (firstEffect !== null) {
  55. let previousLanePriority;
  56. if (decoupleUpdatePriorityFromScheduler) {
  57. previousLanePriority = getCurrentUpdateLanePriority();
  58. setCurrentUpdateLanePriority(SyncLanePriority);
  59. }
  60. const prevExecutionContext = executionContext;
  61. executionContext |= CommitContext;
  62. const prevInteractions = pushInteractions(root);
  63. ReactCurrentOwner.current = null;
  64. focusedInstanceHandle = prepareForCommit(root.containerInfo);
  65. shouldFireAfterActiveInstanceBlur = false;
  66. nextEffect = firstEffect;
  67. //beforemutation执行的工作
  68. do {
  69. try {
  70. commitBeforeMutationEffects();
  71. } catch (error) {
  72. invariant(nextEffect !== null, 'Should be working on an effect.');
  73. captureCommitPhaseError(nextEffect, error);
  74. nextEffect = nextEffect.nextEffect;
  75. }
  76. } while (nextEffect !== null);
  77. focusedInstanceHandle = null;
  78. if (enableProfilerTimer) {
  79. recordCommitTime();
  80. }
  81. nextEffect = firstEffect;
  82. //mutation阶段执行的工作
  83. do {
  84. try {
  85. commitMutationEffects(root, renderPriorityLevel);
  86. } catch (error) {
  87. invariant(nextEffect !== null, 'Should be working on an effect.');
  88. captureCommitPhaseError(nextEffect, error);
  89. nextEffect = nextEffect.nextEffect;
  90. }
  91. } while (nextEffect !== null);
  92. if (shouldFireAfterActiveInstanceBlur) {
  93. afterActiveInstanceBlur();
  94. }
  95. resetAfterCommit(root.containerInfo);
  96. root.current = finishedWork;
  97. // The next phase is the layout phase, where we call effects that read
  98. // the host tree after it's been mutated. The idiomatic use case for this is
  99. // layout, but class component lifecycles also fire here for legacy reasons.
  100. nextEffect = firstEffect;
  101. //layout阶段的工作
  102. do {
  103. try {
  104. commitLayoutEffects(root, lanes);
  105. } catch (error) {
  106. invariant(nextEffect !== null, 'Should be working on an effect.');
  107. captureCommitPhaseError(nextEffect, error);
  108. nextEffect = nextEffect.nextEffect;
  109. }
  110. } while (nextEffect !== null);
  111. nextEffect = null;
  112. // Tell Scheduler to yield at the end of the frame, so the browser has an
  113. // opportunity to paint.
  114. requestPaint();
  115. if (enableSchedulerTracing) {
  116. popInteractions(((prevInteractions: any): Set<Interaction>));
  117. }
  118. executionContext = prevExecutionContext;
  119. if (decoupleUpdatePriorityFromScheduler && previousLanePriority != null) {
  120. // Reset the priority to the previous non-sync value.
  121. setCurrentUpdateLanePriority(previousLanePriority);
  122. }
  123. } else {
  124. // No effects.
  125. root.current = finishedWork;
  126. // Measure these anyway so the flamegraph explicitly shows that there were
  127. // no effects.
  128. // TODO: Maybe there's a better way to report this.
  129. if (enableProfilerTimer) {
  130. recordCommitTime();
  131. }
  132. }
  133. const rootDidHavePassiveEffects = rootDoesHavePassiveEffects;
  134. if (rootDoesHavePassiveEffects) {
  135. // This commit has passive effects. Stash a reference to them. But don't
  136. // schedule a callback until after flushing layout work.
  137. //和本次更新中的useEffect调用有关
  138. rootDoesHavePassiveEffects = false;
  139. rootWithPendingPassiveEffects = root;
  140. pendingPassiveEffectsLanes = lanes;
  141. pendingPassiveEffectsRenderPriority = renderPriorityLevel;
  142. } else {
  143. // We are done with the effect chain at this point so let's clear the
  144. // nextEffect pointers to assist with GC. If we have passive effects, we'll
  145. // clear this in flushPassiveEffects.
  146. //本次更新不存在useEffect的调用,清理链表,垃圾回收
  147. nextEffect = firstEffect;
  148. while (nextEffect !== null) {
  149. const nextNextEffect = nextEffect.nextEffect;
  150. nextEffect.nextEffect = null;
  151. if (nextEffect.flags & Deletion) {
  152. detachFiberAfterEffects(nextEffect);
  153. }
  154. nextEffect = nextNextEffect;
  155. }
  156. }
  157. // Read this again, since an effect might have updated it
  158. remainingLanes = root.pendingLanes;
  159. // Check if there's remaining work on this root
  160. if (remainingLanes !== NoLanes) {
  161. if (enableSchedulerTracing) {
  162. if (spawnedWorkDuringRender !== null) {
  163. const expirationTimes = spawnedWorkDuringRender;
  164. spawnedWorkDuringRender = null;
  165. for (let i = 0; i < expirationTimes.length; i++) {
  166. scheduleInteractions(
  167. root,
  168. expirationTimes[i],
  169. root.memoizedInteractions,
  170. );
  171. }
  172. }
  173. schedulePendingInteractions(root, remainingLanes);
  174. }
  175. } else {
  176. // If there's no remaining work, we can clear the set of already failed
  177. // error boundaries.
  178. legacyErrorBoundariesThatAlreadyFailed = null;
  179. }
  180. if (enableSchedulerTracing) {
  181. if (!rootDidHavePassiveEffects) {
  182. .
  183. finishPendingInteractions(root, lanes);
  184. }
  185. }
  186. if (remainingLanes === SyncLane) {
  187. //通过计数判断是否是进入了无限循环更新了
  188. if (root === rootWithNestedUpdates) {
  189. nestedUpdateCount++;
  190. } else {
  191. nestedUpdateCount = 0;
  192. rootWithNestedUpdates = root;
  193. }
  194. } else {
  195. nestedUpdateCount = 0;
  196. }
  197. onCommitRootDevTools(finishedWork.stateNode, renderPriorityLevel);
  198. //commit可能会出现新的更新,确保在这个根节点上没有新的更新任务
  199. ensureRootIsScheduled(root, now());
  200. if (hasUncaughtError) {
  201. hasUncaughtError = false;
  202. const error = firstUncaughtError;
  203. firstUncaughtError = null;
  204. throw error;
  205. }
  206. if ((executionContext & LegacyUnbatchedContext) !== NoContext) {
  207. if (enableSchedulingProfiler) {
  208. markCommitStopped();
  209. }
  210. return null;
  211. }
  212. // If layout work was scheduled, flush it now.
  213. //如useLayoutEffect中调用setState,这个就是同步的更新,就会在这里面同步执行
  214. flushSyncCallbackQueue();
  215. if (enableSchedulingProfiler) {
  216. markCommitStopped();
  217. }
  218. return null;
  219. }

before mutation阶段

整个过程就是遍历effectList并调用commitBeforeMutationEffects函数处理

  1. function commitBeforeMutationEffects() {
  2. while (nextEffect !== null) {
  3. const current = nextEffect.alternate;
  4. //处理DOM节点渲染/删除后的 autoFocus、blur 逻辑。
  5. if (!shouldFireAfterActiveInstanceBlur && focusedInstanceHandle !== null) {
  6. if ((nextEffect.flags & Deletion) !== NoFlags) {
  7. if (doesFiberContain(nextEffect, focusedInstanceHandle)) {
  8. shouldFireAfterActiveInstanceBlur = true;
  9. beforeActiveInstanceBlur();
  10. }
  11. } else {
  12. // TODO: Move this out of the hot path using a dedicated effect tag.
  13. if (
  14. nextEffect.tag === SuspenseComponent &&
  15. isSuspenseBoundaryBeingHidden(current, nextEffect) &&
  16. doesFiberContain(nextEffect, focusedInstanceHandle)
  17. ) {
  18. shouldFireAfterActiveInstanceBlur = true;
  19. beforeActiveInstanceBlur();
  20. }
  21. }
  22. }
  23. //调用getSnapshotBeforeUpdate生命周期钩子。
  24. const flags = nextEffect.flags;
  25. if ((flags & Snapshot) !== NoFlags) {
  26. setCurrentDebugFiberInDEV(nextEffect);
  27. commitBeforeMutationEffectOnFiber(current, nextEffect);
  28. resetCurrentDebugFiberInDEV();
  29. }
  30. //FunctionComponent中useEffect对应的Effect,需要调度passiveeffects的回调函数
  31. if ((flags & Passive) !== NoFlags) {
  32. // If there are passive effects, schedule a callback to flush at
  33. // the earliest opportunity.
  34. if (!rootDoesHavePassiveEffects) {
  35. rootDoesHavePassiveEffects = true;
  36. scheduleCallback(NormalSchedulerPriority, () => {
  37. flushPassiveEffects();
  38. return null;
  39. });
  40. }
  41. }
  42. nextEffect = nextEffect.nextEffect;
  43. }
  44. }

整体可以分为三部分:

  • 处理DOM节点渲染/删除后的 autoFocus、blur 逻辑。
  • 调用getSnapshotBeforeUpdate生命周期钩子。
  • 调度useEffect。

这里忽略第一部分,看调用getSnapshotBeforeUpdate生命周期钩子的部分

commitBeforeMutationEffects方法调用getSnapshotBeforeUpdate

commitBeforeMutationEffectOnFiber是commitBeforeMutationLifeCycles的别名。

在该方法内会调用getSnapshotBeforeUpdate。

  1. function commitBeforeMutationLifeCycles(
  2. current: Fiber | null,
  3. finishedWork: Fiber,
  4. ): void {
  5. switch (finishedWork.tag) {
  6. case FunctionComponent:
  7. case ForwardRef:
  8. case SimpleMemoComponent:
  9. case Block: {
  10. return;
  11. }
  12. case ClassComponent: {
  13. //如果存在 Snapshot 生命周期对应的tag
  14. if (finishedWork.flags & Snapshot) {
  15. if (current !== null) {
  16. const prevProps = current.memoizedProps;
  17. const prevState = current.memoizedState;
  18. //取到对应的React Component的实例
  19. const instance = finishedWork.stateNode;
  20. // We could update instance props and state here,
  21. // but instead we rely on them being set during last render.
  22. // TODO: revisit this when we implement resuming.
  23. if (__DEV__) {
  24. if (
  25. finishedWork.type === finishedWork.elementType &&
  26. !didWarnAboutReassigningProps
  27. ) {
  28. if (instance.props !== finishedWork.memoizedProps) {
  29. console.error(
  30. 'Expected %s props to match memoized props before ' +
  31. 'getSnapshotBeforeUpdate. ' +
  32. 'This might either be because of a bug in React, or because ' +
  33. 'a component reassigns its own `this.props`. ' +
  34. 'Please file an issue.',
  35. getComponentName(finishedWork.type) || 'instance',
  36. );
  37. }
  38. if (instance.state !== finishedWork.memoizedState) {
  39. console.error(
  40. 'Expected %s state to match memoized state before ' +
  41. 'getSnapshotBeforeUpdate. ' +
  42. 'This might either be because of a bug in React, or because ' +
  43. 'a component reassigns its own `this.state`. ' +
  44. 'Please file an issue.',
  45. getComponentName(finishedWork.type) || 'instance',
  46. );
  47. }
  48. }
  49. }
  50. const snapshot = instance.getSnapshotBeforeUpdate(
  51. finishedWork.elementType === finishedWork.type
  52. ? prevProps
  53. : resolveDefaultProps(finishedWork.type, prevProps),
  54. prevState,
  55. );
  56. if (__DEV__) {
  57. const didWarnSet = ((didWarnAboutUndefinedSnapshotBeforeUpdate: any): Set<mixed>);
  58. if (snapshot === undefined && !didWarnSet.has(finishedWork.type)) {
  59. didWarnSet.add(finishedWork.type);
  60. console.error(
  61. '%s.getSnapshotBeforeUpdate(): A snapshot value (or null) ' +
  62. 'must be returned. You have returned undefined.',
  63. getComponentName(finishedWork.type),
  64. );
  65. }
  66. }
  67. instance.__reactInternalSnapshotBeforeUpdate = snapshot;
  68. }
  69. }
  70. return;
  71. }
  72. case HostRoot: {
  73. if (supportsMutation) {
  74. if (finishedWork.flags & Snapshot) {
  75. const root = finishedWork.stateNode;
  76. clearContainer(root.containerInfo);
  77. }
  78. }
  79. return;
  80. }
  81. case HostComponent:
  82. case HostText:
  83. case HostPortal:
  84. case IncompleteClassComponent:
  85. // Nothing to do for these component types
  86. return;
  87. }
  88. invariant(
  89. false,
  90. 'This unit of work tag should not have side-effects. This error is ' +
  91. 'likely caused by a bug in React. Please file an issue.',
  92. );
  93. }

这个阶段调用getSnapshotBeforeUpdates生命周期,页面没有可见的更新出现。

调度useEffect

  1. if ((flags & Passive) !== NoFlags) {
  2. // If there are passive effects, schedule a callback to flush at
  3. // the earliest opportunity.
  4. if (!rootDoesHavePassiveEffects) {
  5. rootDoesHavePassiveEffects = true;
  6. scheduleCallback(NormalSchedulerPriority, () => {
  7. flushPassiveEffects();
  8. return null;
  9. });
  10. }
  11. }

scheduleCallback方法Scheduler模块提供,用于以某个优先级异步调度一个回调函数。 这个函数以一个优先级异步执行回调函数,所以在FunctionComponent中存在useEffect,并且回调函数需要触发的情况,会在beforemutation阶段以normal的优先级调度,由于commit阶段是同步执行的,所以useEffect的回调函数会在commit阶段执行完之后异步执行。

在此处,被异步调度的回调函数就是触发useEffect的方法flushPassiveEffects

  1. function flushPassiveEffects(): boolean {
  2. // Returns whether passive effects were flushed.
  3. if (pendingPassiveEffectsLanes !== NoLanes) {
  4. const priority = higherEventPriority(
  5. DefaultEventPriority,
  6. lanesToEventPriority(pendingPassiveEffectsLanes),
  7. );
  8. const previousPriority = getCurrentUpdatePriority();
  9. try {
  10. setCurrentUpdatePriority(priority);
  11. return flushPassiveEffectsImpl();
  12. } finally {
  13. setCurrentUpdatePriority(previousPriority);
  14. }
  15. }
  16. return false;
  17. }

flushPassiveEffects内部会设置优先级,并切调用flushPassiveEffectsImpl

flushPassiveEffectsImpl主要做三件事:

  • 调用该useEffect在上一次render时的销毁函数
  • 调用该useEffect在本次render时的回调函数
  • 如果存在同步任务,不需要等待下次事件循环的宏任务,提前执行他

第一步:执行上一次render时useEffect的销毁函数

useEffect的执行需要保证所有组件useEffect的销毁函数必须都执行完后才能执行任意一个组件的useEffect的回调函数。

这是因为多个组件间可能共用同一个ref。

如果在执行前没有销毁函数,那么在某个组件useEffect的销毁函数中修改的ref.current可能影响另一个组件useEffect的回调函数中的同一个ref的current属性。

useLayoutEffect中也有同样的问题,所以他们都遵循先全部销毁,再执行的顺序。

  1. const unmountEffects = pendingPassiveHookEffectsUnmount;
  2. pendingPassiveHookEffectsUnmount = [];
  3. for (let i = 0; i < unmountEffects.length; i += 2) {
  4. const effect = ((unmountEffects[i]: any): HookEffect);
  5. const fiber = ((unmountEffects[i + 1]: any): Fiber);
  6. const destroy = effect.destroy;
  7. effect.destroy = undefined;
  8. if (typeof destroy === 'function') {
  9. try {
  10. if (
  11. enableProfilerTimer &&
  12. enableProfilerCommitHooks &&
  13. fiber.mode & ProfileMode
  14. ) {
  15. try {
  16. startPassiveEffectTimer();
  17. destroy();
  18. } finally {
  19. recordPassiveEffectDuration(fiber);
  20. }
  21. } else {
  22. destroy();
  23. }
  24. } catch (error) {
  25. invariant(fiber !== null, 'Should be working on an effect.');
  26. captureCommitPhaseError(fiber, error);
  27. }
  28. }
  29. }

获取在unmount时处理的销毁函数中其中pendingPassiveHookEffectsUnmount数组的索引i保存需要销毁的effect,i+1保存该effect对应的fiber。(向pendingPassiveHookEffectsUnmount数组内push数据的操作发生在layout阶段 commitLayoutEffectOnFiber方法内部的schedulePassiveEffects方法中。)

  1. function schedulePassiveEffects(finishedWork: Fiber) {
  2. const updateQueue: FunctionComponentUpdateQueue | null = (finishedWork.updateQueue: any);
  3. const lastEffect = updateQueue !== null ? updateQueue.lastEffect : null;
  4. if (lastEffect !== null) {
  5. const firstEffect = lastEffect.next;
  6. let effect = firstEffect;
  7. do {
  8. const {next, tag} = effect;
  9. if (
  10. (tag & HookPassive) !== NoHookEffect &&
  11. (tag & HookHasEffect) !== NoHookEffect
  12. ) {
  13. // 向`pendingPassiveHookEffectsUnmount`数组内`push`要销毁的effect
  14. enqueuePendingPassiveHookEffectUnmount(finishedWork, effect);
  15. // 向`pendingPassiveHookEffectsMount`数组内`push`要执行回调的effect
  16. enqueuePendingPassiveHookEffectMount(finishedWork, effect);
  17. }
  18. effect = next;
  19. } while (effect !== firstEffect);
  20. }
  21. }

第二步:回调函数的执行

遍历数组,执行useEffect的回调函数

  1. const mountEffects = pendingPassiveHookEffectsMount;
  2. pendingPassiveHookEffectsMount = [];
  3. for (let i = 0; i < mountEffects.length; i += 2) {
  4. const effect = ((mountEffects[i]: any): HookEffect);
  5. const fiber = ((mountEffects[i + 1]: any): Fiber);
  6. try {
  7. const create = effect.create;
  8. if (
  9. enableProfilerTimer &&
  10. enableProfilerCommitHooks &&
  11. fiber.mode & ProfileMode
  12. ) {
  13. try {
  14. startPassiveEffectTimer();
  15. effect.destroy = create();
  16. } finally {
  17. recordPassiveEffectDuration(fiber);
  18. }
  19. } else {
  20. effect.destroy = create();
  21. }
  22. } catch (error) {
  23. invariant(fiber !== null, 'Should be working on an effect.');
  24. captureCommitPhaseError(fiber, error);
  25. }
  26. }

其中向pendingPassiveHookEffectsMount中push数据的操作同样发生在schedulePassiveEffects中。

  1. function schedulePassiveEffects(finishedWork: Fiber) {
  2. const updateQueue: FunctionComponentUpdateQueue | null = (finishedWork.updateQueue: any);
  3. const lastEffect = updateQueue !== null ? updateQueue.lastEffect : null;
  4. if (lastEffect !== null) {
  5. const firstEffect = lastEffect.next;
  6. let effect = firstEffect;
  7. do {
  8. const {next, tag} = effect;
  9. if (
  10. (tag & HookPassive) !== NoHookEffect &&
  11. (tag & HookHasEffect) !== NoHookEffect
  12. ) {
  13. enqueuePendingPassiveHookEffectUnmount(finishedWork, effect);
  14. enqueuePendingPassiveHookEffectMount(finishedWork, effect);
  15. }
  16. effect = next;
  17. } while (effect !== firstEffect);
  18. }
  19. }

需要注意的是:与 componentDidMount、componentDidUpdate 不同的是,在浏览器完成布局与绘制之后,传给 useEffect 的函数会延迟调用。这使得它适用于许多常见的副作用场景,比如设置订阅和事件处理等情况,因此不应在函数中执行阻塞浏览器更新屏幕的操作。

所以useEffect的这两个阶段会在页面渲染后(layout阶段后)异步执行,以防止同步执行阻塞浏览器渲染。

mutation阶段

这个阶段是执行DOM操作的阶段,遍历effectList,执行commitMutationEffects方法。

  1. nextEffect = firstEffect;
  2. do {
  3. try {
  4. commitMutationEffects(root, renderPriorityLevel);
  5. } catch (error) {
  6. invariant(nextEffect !== null, 'Should be working on an effect.');
  7. captureCommitPhaseError(nextEffect, error);
  8. nextEffect = nextEffect.nextEffect;
  9. }
  10. } while (nextEffect !== null);

概览

这部分同样是遍历effectList,根据 ContentReset effectTag重置文字节点,然后更新ref,最后根据effectTag保存的信息进行对应的插入、更新、删除DOM。

  1. function commitMutationEffects(
  2. root: FiberRoot,
  3. renderPriorityLevel: ReactPriorityLevel,
  4. ) {
  5. // TODO: Should probably move the bulk of this function to commitWork.
  6. while (nextEffect !== null) {
  7. setCurrentDebugFiberInDEV(nextEffect);
  8. const flags = nextEffect.flags;
  9. if (flags & ContentReset) {
  10. commitResetTextContent(nextEffect);
  11. }
  12. if (flags & Ref) {
  13. const current = nextEffect.alternate;
  14. if (current !== null) {
  15. commitDetachRef(current);
  16. }
  17. if (enableScopeAPI) {
  18. // TODO: This is a temporary solution that allowed us to transition away
  19. // from React Flare on www.
  20. if (nextEffect.tag === ScopeComponent) {
  21. commitAttachRef(nextEffect);
  22. }
  23. }
  24. }
  25. // The following switch statement is only concerned about placement,
  26. // updates, and deletions. To avoid needing to add a case for every possible
  27. // bitmap value, we remove the secondary effects from the effect tag and
  28. // switch on that value.
  29. const primaryFlags = flags & (Placement | Update | Deletion | Hydrating);
  30. switch (primaryFlags) {
  31. case Placement: {
  32. commitPlacement(nextEffect);
  33. // Clear the "placement" from effect tag so that we know that this is
  34. // inserted, before any life-cycles like componentDidMount gets called.
  35. // TODO: findDOMNode doesn't rely on this any more but isMounted does
  36. // and isMounted is deprecated anyway so we should be able to kill this.
  37. nextEffect.flags &= ~Placement;
  38. break;
  39. }
  40. case PlacementAndUpdate: {
  41. // Placement
  42. commitPlacement(nextEffect);
  43. // Clear the "placement" from effect tag so that we know that this is
  44. // inserted, before any life-cycles like componentDidMount gets called.
  45. nextEffect.flags &= ~Placement;
  46. // Update
  47. const current = nextEffect.alternate;
  48. commitWork(current, nextEffect);
  49. break;
  50. }
  51. case Hydrating: {
  52. nextEffect.flags &= ~Hydrating;
  53. break;
  54. }
  55. case HydratingAndUpdate: {
  56. nextEffect.flags &= ~Hydrating;
  57. // Update
  58. const current = nextEffect.alternate;
  59. commitWork(current, nextEffect);
  60. break;
  61. }
  62. case Update: {
  63. const current = nextEffect.alternate;
  64. commitWork(current, nextEffect);
  65. break;
  66. }
  67. case Deletion: {
  68. commitDeletion(root, nextEffect, renderPriorityLevel);
  69. break;
  70. }
  71. }
  72. resetCurrentDebugFiberInDEV();
  73. nextEffect = nextEffect.nextEffect;
  74. }
  75. }

根据以上代码总结,对每个有副作用的Fiber节点执行如下三个操作:

  • 根据ContentReset effectTag重置文字节点
  • 更新ref
  • 根据effectTag分别处理,其中effectTag包括(Placement | Update | Deletion | Hydrating(SSR))

Placement

commitPlacement

该方法所做的工作分为三步:
1.获取父级DOM节点。其中finishedWork为传入的Fiber节点

  1. const parentFiber = getHostParentFiber(finishedWork);
  2. const parentStateNode = parentFiber.stateNode;

2.获取Fiber节点的DOM兄弟节点

  1. const before = getHostSibling(finishedWork);

3.根据DOM兄弟节点是否存在决定调用parentNode.insertBeforeparentNode.appendChild执行DOM插入操作。

  1. if (isContainer) {
  2. insertOrAppendPlacementNodeIntoContainer(finishedWork, before, parent);
  3. } else {
  4. insertOrAppendPlacementNode(finishedWork, before, parent);
  5. }

getHostSibling(获取兄弟DOM节点)的执行很耗时,当在同一个父Fiber节点下依次执行多个插入操作,getHostSibling算法的复杂度为指数级。

这是由于Fiber节点不只包括HostComponent,所以Fiber树和渲染的DOM树节点并不是一一对应的。要从Fiber节点找到DOM节点很可能跨层级遍历。

  1. function Item() {
  2. return <li><li>;
  3. }
  4. function App() {
  5. return (
  6. <div>
  7. <Item/>
  8. </div>
  9. )
  10. }
  11. ReactDOM.render(<App/>, document.getElementById('root'));

对应的Fiber树和DOM树结构为:

  1. // Fiber树
  2. child child child child
  3. rootFiber -----> App -----> div -----> Item -----> li
  4. // DOM树
  5. #root ---> div ---> li

当在div的子节点Item前插入一个新节点p,即App变为:

  1. function App() {
  2. return (
  3. <div>
  4. <p></p>
  5. <Item/>
  6. </div>
  7. )
  8. }

对应的Fiber树和DOM树结构为:

  1. // Fiber树
  2. child child child
  3. rootFiber -----> App -----> div -----> p
  4. | sibling child
  5. | -------> Item -----> li
  6. // DOM树
  7. #root ---> div ---> p
  8. |
  9. ---> li

此时DOM节点 p的兄弟节点为li,而Fiber节点 p对应的兄弟DOM节点为:

  1. fiberP.sibling.child

fiber p兄弟fiber Item子fiber li

Update

调用的是commitWork

在这里主要关注函数组件和原始类型组件

FunctionComponent mutation

调用 commitHookEffectListUnmount()方法,该方法会遍历effectList,执行所有useLayoutEffect hook的销毁函数。

  1. function commitHookEffectListUnmount(tag: number, finishedWork: Fiber) {
  2. const updateQueue: FunctionComponentUpdateQueue | null = (finishedWork.updateQueue: any);
  3. const lastEffect = updateQueue !== null ? updateQueue.lastEffect : null;
  4. if (lastEffect !== null) {
  5. const firstEffect = lastEffect.next;
  6. let effect = firstEffect;
  7. do {
  8. if ((effect.tag & tag) === tag) {
  9. // Unmount
  10. const destroy = effect.destroy;
  11. effect.destroy = undefined;
  12. if (destroy !== undefined) {
  13. destroy();
  14. }
  15. }
  16. effect = effect.next;
  17. } while (effect !== firstEffect);
  18. }
  19. }

HostComponent mutation

调用的是commitUpdate

commitUpdate->updateProperties->updateDOMProperties

最终会在updateDOMProperties中将render阶段 completeWork (opens new window)中为Fiber节点赋值的updateQueue对应的内容渲染在页面上。

  1. function updateDOMProperties(
  2. domElement: Element,
  3. updatePayload: Array<any>,
  4. wasCustomComponentTag: boolean,
  5. isCustomComponentTag: boolean,
  6. ): void {
  7. // TODO: Handle wasCustomComponentTag
  8. for (let i = 0; i < updatePayload.length; i += 2) {
  9. const propKey = updatePayload[i];
  10. const propValue = updatePayload[i + 1];
  11. if (propKey === STYLE) {
  12. // 处理 style
  13. setValueForStyles(domElement, propValue);
  14. } else if (propKey === DANGEROUSLY_SET_INNER_HTML) {
  15. //处理 DANGEROUSLY_SET_INNER_HTML
  16. setInnerHTML(domElement, propValue);
  17. } else if (propKey === CHILDREN) {
  18. // 处理 children
  19. setTextContent(domElement, propValue);
  20. } else {
  21. // 处理剩余 props
  22. setValueForProperty(domElement, propKey, propValue, isCustomComponentTag);
  23. }
  24. }
  25. }

Deletion

如果需要删除节点就会执行commitDeletion方法

  1. function commitDeletion(
  2. finishedRoot: FiberRoot,
  3. current: Fiber,
  4. renderPriorityLevel: ReactPriorityLevel,
  5. ): void {
  6. if (supportsMutation) {
  7. // Recursively delete all host nodes from the parent.
  8. // Detach refs and call componentWillUnmount() on the whole subtree.
  9. unmountHostComponents(finishedRoot, current, renderPriorityLevel);
  10. } else {
  11. // Detach refs and call componentWillUnmount() on the whole subtree.
  12. commitNestedUnmounts(finishedRoot, current, renderPriorityLevel);
  13. }
  14. const alternate = current.alternate;
  15. detachFiberMutation(current);
  16. if (alternate !== null) {
  17. detachFiberMutation(alternate);
  18. }
  19. }

unmountHostComponents方法中先会获取当前节点的父节点,因为想删除某个节点,需要找到他的父节点

  1. let node: Fiber = current;
  2. // Each iteration, currentParent is populated with node's host parent if not
  3. // currentParentIsValid.
  4. let currentParentIsValid = false;
  5. // Note: these two variables *must* always be updated together.
  6. let currentParent;
  7. let currentParentIsContainer;
  8. while (true) {
  9. if (!currentParentIsValid) {
  10. let parent = node.return;
  11. findParent: while (true) {
  12. invariant(
  13. parent !== null,
  14. 'Expected to find a host parent. This error is likely caused by ' +
  15. 'a bug in React. Please file an issue.',
  16. );
  17. const parentStateNode = parent.stateNode;
  18. switch (parent.tag) {
  19. case HostComponent:
  20. currentParent = parentStateNode;
  21. currentParentIsContainer = false;
  22. break findParent;
  23. case HostRoot:
  24. currentParent = parentStateNode.containerInfo;
  25. currentParentIsContainer = true;
  26. break findParent;
  27. case HostPortal:
  28. currentParent = parentStateNode.containerInfo;
  29. currentParentIsContainer = true;
  30. break findParent;
  31. case FundamentalComponent:
  32. if (enableFundamentalAPI) {
  33. currentParent = parentStateNode.instance;
  34. currentParentIsContainer = false;
  35. }
  36. }
  37. parent = parent.return;
  38. }
  39. currentParentIsValid = true;
  40. }

找到之后会执行commitNestedUnmounts方法,这个方法嵌套了commitUnmout方法,因为当我们删除一个节点,这个节点可能包含一颗子树,一颗子树中的子孙Fiber节点都需要被递归的删除。

对于FunctionComponent方法,会注册需要被执行的useEffect的回调函数,如果存在useEffect的销毁函数存在,也会执行销毁函数。

对于ClassComponent方法,会调用componentWillUnmount生命周期钩子

综上:

layout阶段

commitLayoutEffects

  1. while (nextEffect !== null) {
  2. setCurrentDebugFiberInDEV(nextEffect);
  3. const flags = nextEffect.flags;
  4. // 调用生命周期钩子和hook
  5. if (flags & (Update | Callback)) {
  6. const current = nextEffect.alternate;
  7. commitLayoutEffectOnFiber(root, current, nextEffect, committedLanes);
  8. }
  9. if (enableScopeAPI) {
  10. // 赋值ref
  11. if (flags & Ref && nextEffect.tag !== ScopeComponent) {
  12. commitAttachRef(nextEffect);
  13. }
  14. } else {
  15. if (flags & Ref) {
  16. commitAttachRef(nextEffect);
  17. }
  18. }
  19. resetCurrentDebugFiberInDEV();
  20. nextEffect = nextEffect.nextEffect;
  21. }

这里的commitLayoutEffectOnFiber是commitLifeCycles方法

与前两个阶段类似,layout阶段也是遍历effectList,最终执行 commitLayoutEffectOnFiber方法

  • 对于ClassComponent,他会通过current === null?区分是mount还是update,调用componentDidMountcomponentDidUpdate
  • 触发状态更新的this.setState的第二个参数回调函数,也会在此时调用
  • 对于FunctionComponent及ForwardRef、React.memo包裹的FunctionComponent,他在这里会调用useLayoutEffect hook的回调函数,因为参数是HookLayout | HookHasEffect,所以只处理由useLayoutEffect()创建的effect,调用effect.create()之后, 将返回值赋值到effect.destroy,并且添加useEffect的销毁函数和回调函数到队列
  • 对于HostRoot,即rootFiber,如果赋值了第三个参数回调函数,也会在此时调用

commitAttachRef

获取DOM实例,更新ref

  1. function commitAttachRef(finishedWork: Fiber) {
  2. const ref = finishedWork.ref;
  3. if (ref !== null) {
  4. const instance = finishedWork.stateNode;
  5. let instanceToUse;
  6. switch (finishedWork.tag) {
  7. case HostComponent:
  8. instanceToUse = getPublicInstance(instance);
  9. break;
  10. default:
  11. instanceToUse = instance;
  12. }
  13. // Moved outside to ensure DCE works with this flag
  14. if (enableScopeAPI && finishedWork.tag === ScopeComponent) {
  15. instanceToUse = instance;
  16. }
  17. if (typeof ref === 'function') {
  18. ref(instanceToUse);
  19. } else {
  20. if (__DEV__) {
  21. if (!ref.hasOwnProperty('current')) {
  22. console.error(
  23. 'Unexpected ref object provided for %s. ' +
  24. 'Use either a ref-setter function or React.createRef().',
  25. getComponentName(finishedWork.type),
  26. );
  27. }
  28. }
  29. ref.current = instanceToUse;
  30. }
  31. }
  32. }

current Fiber树切换

  1. root.current = finishedWork;

标志着layout阶段结束。

componentWillUnmount会在mutation阶段执行,此时current Fiber树还指向前一次更新的Fiber树,在生命周期钩子内获取的DOM还是更新前的。

componentDidMountcomponentDidUpdate会在layout阶段执行。此时current Fiber树已经指向更新后Fiber树,在生命周期钩子内获取的DOM就是更新后的。