createWorkInProgress创建workInPogress Fiber或者复用已有的current Fiber节点

初次渲染

image.png
首次执行reactDom.render时,会创建整个应用的根节点,然后FiberRootNode的current指向rootFiber当前应用的根节点。
双缓存机制-首屏渲染的逻辑中,会基于current rootFiber 建立workInProgress rootFiber 。
首屏渲染中,第一次进入createWorkInProgress的current是div #root,还current Fiber没有alternate,此时workInProgress Fiber为null。
image.png
createFiber 创建一个新的fiber节点,并将current Fiber的参数都赋值给workInProgress Fiber,并返回workInProgress Fiber

  1. function createWorkInProgress(current, pendingProps) {
  2. var workInProgress = current.alternate;
  3. if (workInProgress === null) {
  4. //createFiber 创建一个新的fiber节点
  5. workInProgress = createFiber(current.tag, pendingProps, current.key, current.mode);
  6. //将current Fiber的参数都赋值给workInProgress Fiber
  7. workInProgress.elementType = current.elementType;
  8. workInProgress.type = current.type;
  9. workInProgress.stateNode = current.stateNode;
  10. ...
  11. //将current指向workInProgress的alternate
  12. workInProgress.alternate = current;
  13. //将workInProgress指向current.alternate
  14. current.alternate = workInProgress;
  15. } else {
  16. workInProgress.pendingProps = pendingProps; // Needed because Blocks store data on type.
  17. workInProgress.type = current.type; // We already have an alternate.
  18. // Reset the effect tag.
  19. workInProgress.flags = NoFlags; // The effects are no longer valid.
  20. workInProgress.subtreeFlags = NoFlags;
  21. workInProgress.deletions = null;
  22. {
  23. // We intentionally reset, rather than copy, actualDuration & actualStartTime.
  24. // This prevents time from endlessly accumulating in new commits.
  25. // This has the downside of resetting values for different priority renders,
  26. // But works for yielding (the common case) and should support resuming.
  27. workInProgress.actualDuration = 0;
  28. workInProgress.actualStartTime = -1;
  29. }
  30. } // Reset all effects except static ones.
  31. // Static effects are not specific to a render.
  32. //将workInProgress参数赋值为current参数。
  33. workInProgress.flags = current.flags & StaticMask;
  34. workInProgress.childLanes = current.childLanes;
  35. workInProgress.lanes = current.lanes;
  36. workInProgress.child = current.child;
  37. workInProgress.memoizedProps = current.memoizedProps;
  38. workInProgress.memoizedState = current.memoizedState;
  39. workInProgress.updateQueue = current.updateQueue; // Clone the dependencies object. This is mutated during the render phase, so
  40. // it cannot be shared with the current fiber.
  41. var currentDependencies = current.dependencies;
  42. workInProgress.dependencies = currentDependencies === null ? null : {
  43. lanes: currentDependencies.lanes,
  44. firstContext: currentDependencies.firstContext
  45. }; // These will be overridden during the parent's reconciliation
  46. //将current.sibling赋值workInProgress的sibling等等
  47. workInProgress.sibling = current.sibling;
  48. workInProgress.index = current.index;
  49. workInProgress.ref = current.ref;
  50. {
  51. workInProgress.selfBaseDuration = current.selfBaseDuration;
  52. workInProgress.treeBaseDuration = current.treeBaseDuration;
  53. }
  54. {
  55. workInProgress._debugNeedsRemount = current._debugNeedsRemount;
  56. switch (workInProgress.tag) {
  57. case IndeterminateComponent:
  58. case FunctionComponent:
  59. case SimpleMemoComponent:
  60. workInProgress.type = resolveFunctionForHotReloading(current.type);
  61. break;
  62. case ClassComponent:
  63. workInProgress.type = resolveClassForHotReloading(current.type);
  64. break;
  65. case ForwardRef:
  66. workInProgress.type = resolveForwardRefForHotReloading(current.type);
  67. break;
  68. }
  69. }
  70. return workInProgress;
  71. } // Used to reuse a Fiber for a second pass.

在beginWork时,依次执行,执行到div时,由于div是一个HostComponent
image.png
执行reconcileChildren前,workInProgress为null
image.png
执行完,此时workInProgress.child就是一个新的Fiber节点
image.png
执行完reconcileChildren后,将div就有了header节点
image.png
整个render执行完之后,就进行commit,将FiberRootNode的current执行workInProgress的RootFiber上,这样workInprogress Fiber树就变成了current Fiber树。
image.png

第一次更新

点击p标签,更新时,current指向的是右边的rootFiber,前文中,current.alternate指向的是左边workInProgress的rootFiber。因此workInProgress rootFiber不为null,会复用workInProgress节点。
以app下的子节点div举例,初次渲染是,不存在current节点,但是更新时,同时存在current与workInProgress节点。

  1. function reconcileChildren(current, workInProgress, nextChildren, renderLanes) {
  2. if (current === null) {
  3. workInProgress.child = mountChildFibers(workInProgress, null, nextChildren, renderLanes);
  4. } else {
  5. workInProgress.child = reconcileChildFibers(workInProgress, current.child, nextChildren, renderLanes);
  6. }
  7. }

当执行到div这个节点时,workInProgress的rootFiber、APP节点已经创建完成。
image.png
执行reconcileChildFibers,创建workInProgress div的child时,会复用左边current的一些属性,包括header子节点。因此workInProgress div有了自己的子节点,当执行reconcileChildren时,header会创建自己的子节点。

image.png
当所有的都渲染完成以后,FiberRootNode的current指针,指向左边的rootFiber。
image.png

第二次更新

第二次更新与第一次的区别?

根据流程图,第二次更新时,每一个current节点,都已经存在了对应的workInProgress的alternate指针。(这就是双缓存机制的原理)
image.png

“递”阶段update流程 -beginWork

第一个进入beginWork的tag为3,是rootFIber

  1. function beginWork(current, workInProgress, renderLanes) {
  2. {
  3. ...
  4. if (current !== null) {
  5. var oldProps = current.memoizedProps;
  6. var newProps = workInProgress.pendingProps;
  7. //根据新旧props,context,type是否相同,来判断这个节点是否有变化,
  8. if (oldProps !== newProps || hasContextChanged() || ( // Force a re-render if the implementation changed due to hot reload:
  9. workInProgress.type !== current.type )) {
  10. // 标记这个节点是否有变化
  11. didReceiveUpdate = true;
  12. } else {
  13. //此段代码表示本次更新中是否有任务,当前rootFiber没有任务,因此didReceiveUpdate为false,
  14. //并进入attemptEarlyBailoutIfNoScheduledUpdate,不再像首屏渲染时,进入Fiber节点的Update逻辑
  15. var hasScheduledUpdateOrContext = checkScheduledUpdateOrContext(current, renderLanes);
  16. if (!hasScheduledUpdateOrContext && // If this is the second pass of an error or suspense boundary, there
  17. // may not be work scheduled on `current`, so we check for this flag.
  18. (workInProgress.flags & DidCapture) === NoFlags) {
  19. // No pending updates or context. Bail out now.
  20. didReceiveUpdate = false;
  21. return attemptEarlyBailoutIfNoScheduledUpdate(current, workInProgress, renderLanes);
  22. }
  23. if ((current.flags & ForceUpdateForLegacySuspense) !== NoFlags) {
  24. // This is a special case that only exists for legacy mode.
  25. // See https://github.com/facebook/react/pull/19216.
  26. didReceiveUpdate = true;
  27. } else {
  28. // An update was scheduled on this fiber, but there are no new props
  29. // nor legacy context. Set this to false. If an update queue or context
  30. // consumer produces a changed value, it will set this to true. Otherwise,
  31. // the component will assume the children have not changed and bail out.
  32. didReceiveUpdate = false;
  33. }
  34. }
  35. } else {
  36. didReceiveUpdate = false;
  37. ...
  38. }
  39. ....//update逻辑
  40. }
  41. }

当没有任务需要执行时,会直接进入bailoutOnAlreadyFinishedWork,克隆一份workInProgress的child

attemptEarlyBailoutIfNoScheduledUpdate

当前rootFiber没有任务,因此didReceiveUpdate为false,并进入attemptEarlyBailoutIfNoScheduledUpdate,不再像首屏渲染时,进入Fiber节点的Update逻辑
image.png

bailoutOnAlreadyFinishedWork

最终进入bailoutOnAlreadyFinishedWork方法
image.png

cloneChildFibers

image.png

createWorkInProgress

createWorkInProgress创建新的或者复用已有的currentFiber节点,并将这个child作为workInProgress的child。
image.png

有任务执行时

继续执行beginWork,当App进入时,跳过attemptEarlyBailoutIfNoScheduledUpdate,继续往下执行
image.png
进入updateFunctionComponent的逻辑
image.png

updateFunctionComponent

image.png

在updateFunctionComponent会调用renderWithHooks的方法

renderWithHooks

会调用Component方法,执行App(),进入App 代码的逻辑,返回jsx对象
image.png
image.png
image.png
此时的child已经返回了App 的jsx对象。
image.png
继续执行,renderWithHooks返回给nextChild

reconcileChildren:返回workInProgress的child,基于当前workInProgress Fiber与nextChild的子jsx对象形成一个新的WorkInProgress Fiber。

image.png

reconcileChildren

image.png

reconcileChildFibers

根据不同的type进入不同的reconcile逻辑
image.png

placeSingleChild:首屏渲染shouldTrackSideEffects为false,更新时为true。

此次更新不会进入里面,因为newFiber,即workInProgress的alternate不为null
image.png

什么情况下workInProgress.alternate为null?

由于workInProgress的alternate指向的是current的alternate,而current.alternate为null,表示当前fiber节点在上一个更新中不存在Fiber节点,是在本次更新中新创建的Fiber节点,所以要将新创建的Fiber节点的dom挂载在页面中。

“递”阶段mount与update的区别?

1、在beginWorkwork开始时,中有一个优化的逻辑,如果命中优化的逻辑,会进入bailoutOnAlreadyFinishedWork,克隆一份workInProgress的child。
2、如果没有命中,会继续走不同Fiber节点的Update的逻辑,在Update的逻辑中进入reconcile的逻辑(
reconcileChildren),在这个逻辑中,会将current的Fiber节点与nextChild的Jsx做对比,产生一个新的Fiber节点。
beginWork.png