修改app.js代码,将num放入code标签的title属性中。

  1. import "./App.css"
  2. import { useState } from "react"
  3. function App() {
  4. const [num, setNum] = useState(0)
  5. const handleNum = () => {
  6. let _num = num + 1
  7. setNum(_num)
  8. }
  9. return (
  10. <div className="App">
  11. <header className="App-header">
  12. <p onClick={handleNum}>
  13. <code title={num}>{num}</code>
  14. </p>
  15. <a className="App-link" href="https://reactjs.org" target="_blank" rel="noopener noreferrer">
  16. Learn React
  17. </a>
  18. </header>
  19. </div>
  20. )
  21. }
  22. export default App

初次渲染

在首屏渲染中,comleteWork,根据不同的tag进入不同的component中,code的tag为5,进入hostComponent中,因为current为null,会进入createInstance,创建新的dom元素。

  1. function completeWork(current, workInProgress, renderLanes) {
  2. var newProps = workInProgress.pendingProps; // Note: This intentionally doesn't check if we're hydrating because comparing
  3. // to the current tree provider fiber is just as fast and less error-prone.
  4. // Ideally we would have a special version of the work loop only
  5. // for hydration.
  6. popTreeContext(workInProgress);
  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. ...
  22. case HostComponent:
  23. {
  24. popHostContext(workInProgress);
  25. var rootContainerInstance = getRootHostContainer();
  26. var type = workInProgress.type;
  27. if (current !== null && workInProgress.stateNode != null) {
  28. updateHostComponent$1(current, workInProgress, type, newProps, rootContainerInstance);
  29. if (current.ref !== workInProgress.ref) {
  30. markRef$1(workInProgress);
  31. }
  32. } else {
  33. if (!newProps) {
  34. if (workInProgress.stateNode === null) {
  35. throw new Error('We must have new props for new mounts. This error is likely ' + 'caused by a bug in React. Please file an issue.');
  36. } // This can happen when we abort work.
  37. bubbleProperties(workInProgress);
  38. return null;
  39. }
  40. var currentHostContext = getHostContext(); // TODO: Move createInstance to beginWork and keep it on a context
  41. // "stack" as the parent. Then append children as we go in beginWork
  42. // or completeWork depending on whether we want to add them top->down or
  43. // bottom->up. Top->down is faster in IE11.
  44. var _wasHydrated = popHydrationState(workInProgress);
  45. if (_wasHydrated) {
  46. // TODO: Move this and createInstance step into the beginPhase
  47. // to consolidate.
  48. if (prepareToHydrateHostInstance(workInProgress, rootContainerInstance, currentHostContext)) {
  49. // If changes to the hydrated node need to be applied at the
  50. // commit-phase we mark this as such.
  51. markUpdate(workInProgress);
  52. }
  53. } else {
  54. var instance = createInstance(type, newProps, rootContainerInstance, currentHostContext, workInProgress);
  55. appendAllChildren(instance, workInProgress, false, false);
  56. workInProgress.stateNode = instance; // Certain renderers require commit-time effects for initial mount.
  57. // (eg DOM renderer supports auto-focus for certain elements).
  58. // Make sure such renderers get scheduled for later work.
  59. if (finalizeInitialChildren(instance, type, newProps, rootContainerInstance)) {
  60. markUpdate(workInProgress);
  61. }
  62. }
  63. if (workInProgress.ref !== null) {
  64. // If there is a ref on a host node we need to schedule a callback
  65. markRef$1(workInProgress);
  66. }
  67. }
  68. bubbleProperties(workInProgress);
  69. return null;
  70. }
  71. ...
  72. }
  73. }

image.png

触发更新

updateHostComponent$1

触发更新时,current不为null,进入 updateHostComponent$1中
image.png

prepareUpdate

image.png

diffProperties

  1. function diffProperties(domElement, tag, lastRawProps, nextRawProps, rootContainerElement) {
  2. {
  3. validatePropertiesInDevelopment(tag, nextRawProps);
  4. }
  5. var updatePayload = null;
  6. var lastProps;
  7. var nextProps;
  8. //根据不同的tag进入不同逻辑,当前tag为code不为表单属性,进入default
  9. switch (tag) {
  10. case 'input':
  11. lastProps = getHostProps(domElement, lastRawProps);
  12. nextProps = getHostProps(domElement, nextRawProps);
  13. updatePayload = [];
  14. break;
  15. case 'select':
  16. lastProps = getHostProps$1(domElement, lastRawProps);
  17. nextProps = getHostProps$1(domElement, nextRawProps);
  18. updatePayload = [];
  19. break;
  20. case 'textarea':
  21. lastProps = getHostProps$2(domElement, lastRawProps);
  22. nextProps = getHostProps$2(domElement, nextRawProps);
  23. updatePayload = [];
  24. break;
  25. default:
  26. lastProps = lastRawProps;
  27. nextProps = nextRawProps;
  28. if (typeof lastProps.onClick !== 'function' && typeof nextProps.onClick === 'function') {
  29. // TODO: This cast may not be sound for SVG, MathML or custom elements.
  30. trapClickOnNonInteractiveElement(domElement);
  31. }
  32. break;
  33. }
  34. //检验属性合法性
  35. assertValidProps(tag, nextProps);
  36. var propKey;
  37. var styleName;
  38. var styleUpdates = null;
  39. //遍历上一次属性
  40. for (propKey in lastProps) {
  41. if (nextProps.hasOwnProperty(propKey) || !lastProps.hasOwnProperty(propKey) || lastProps[propKey] == null) {
  42. continue;
  43. }
  44. if (propKey === STYLE) {
  45. var lastStyle = lastProps[propKey];
  46. for (styleName in lastStyle) {
  47. if (lastStyle.hasOwnProperty(styleName)) {
  48. if (!styleUpdates) {
  49. styleUpdates = {};
  50. }
  51. styleUpdates[styleName] = '';
  52. }
  53. }
  54. } else if (propKey === DANGEROUSLY_SET_INNER_HTML || propKey === CHILDREN) ; else if (propKey === SUPPRESS_CONTENT_EDITABLE_WARNING || propKey === SUPPRESS_HYDRATION_WARNING) ; else if (propKey === AUTOFOCUS) ; else if (registrationNameDependencies.hasOwnProperty(propKey)) {
  55. // This is a special case. If any listener updates we need to ensure
  56. // that the "current" fiber pointer gets updated so we need a commit
  57. // to update this element.
  58. if (!updatePayload) {
  59. updatePayload = [];
  60. }
  61. } else {
  62. // For all other deleted properties we add it to the queue. We use
  63. // the allowed property list in the commit phase instead.
  64. (updatePayload = updatePayload || []).push(propKey, null);
  65. }
  66. }
  67. //遍历下一次属性
  68. for (propKey in nextProps) {
  69. //对应的值
  70. var nextProp = nextProps[propKey];
  71. var lastProp = lastProps != null ? lastProps[propKey] : undefined;
  72. if (!nextProps.hasOwnProperty(propKey) || nextProp === lastProp || nextProp == null && lastProp == null) {
  73. continue;
  74. }
  75. if (propKey === STYLE) {
  76. {
  77. if (nextProp) {
  78. // Freeze the next style object so that we can assume it won't be
  79. // mutated. We have already warned for this in the past.
  80. Object.freeze(nextProp);
  81. }
  82. }
  83. if (lastProp) {
  84. // Unset styles on `lastProp` but not on `nextProp`.
  85. for (styleName in lastProp) {
  86. if (lastProp.hasOwnProperty(styleName) && (!nextProp || !nextProp.hasOwnProperty(styleName))) {
  87. if (!styleUpdates) {
  88. styleUpdates = {};
  89. }
  90. styleUpdates[styleName] = '';
  91. }
  92. } // Update styles that changed since `lastProp`.
  93. for (styleName in nextProp) {
  94. if (nextProp.hasOwnProperty(styleName) && lastProp[styleName] !== nextProp[styleName]) {
  95. if (!styleUpdates) {
  96. styleUpdates = {};
  97. }
  98. styleUpdates[styleName] = nextProp[styleName];
  99. }
  100. }
  101. } else {
  102. // Relies on `updateStylesByID` not mutating `styleUpdates`.
  103. if (!styleUpdates) {
  104. if (!updatePayload) {
  105. updatePayload = [];
  106. }
  107. updatePayload.push(propKey, styleUpdates);
  108. }
  109. styleUpdates = nextProp;
  110. }
  111. } else if (propKey === DANGEROUSLY_SET_INNER_HTML) {
  112. var nextHtml = nextProp ? nextProp[HTML$1] : undefined;
  113. var lastHtml = lastProp ? lastProp[HTML$1] : undefined;
  114. if (nextHtml != null) {
  115. if (lastHtml !== nextHtml) {
  116. (updatePayload = updatePayload || []).push(propKey, nextHtml);
  117. }
  118. }
  119. } else if (propKey === CHILDREN) {
  120. if (typeof nextProp === 'string' || typeof nextProp === 'number') {
  121. (updatePayload = updatePayload || []).push(propKey, '' + nextProp);
  122. }
  123. } else if (propKey === SUPPRESS_CONTENT_EDITABLE_WARNING || propKey === SUPPRESS_HYDRATION_WARNING) ; else if (registrationNameDependencies.hasOwnProperty(propKey)) {
  124. if (nextProp != null) {
  125. // We eagerly listen to this even though we haven't committed yet.
  126. if ( typeof nextProp !== 'function') {
  127. warnForInvalidEventListener(propKey, nextProp);
  128. }
  129. if (propKey === 'onScroll') {
  130. listenToNonDelegatedEvent('scroll', domElement);
  131. }
  132. }
  133. if (!updatePayload && lastProp !== nextProp) {
  134. // This is a special case. If any listener updates we need to ensure
  135. // that the "current" props pointer gets updated so we need a commit
  136. // to update this element.
  137. updatePayload = [];
  138. }
  139. } else {
  140. // For any other property we always add it to the queue and then we
  141. // filter it out using the allowed property list during the commit.
  142. (updatePayload = updatePayload || []).push(propKey, nextProp);
  143. }
  144. }
  145. if (styleUpdates) {
  146. {
  147. validateShorthandPropertyCollisionInDev(styleUpdates, nextProps[STYLE]);
  148. }
  149. (updatePayload = updatePayload || []).push(STYLE, styleUpdates);
  150. }
  151. return updatePayload;
  152. } // Apply the diff.

image.png

for (propKey in lastProps)

lastProps有两个属性,一个是title,一个是children,其中值都为0
image.png
终止的情况是,nextProps存在propKey或者lastProps不存在propKey或者 lastProps[propKey] == null,下方的循环争对lastProps的propKey对应的value被删除的情况,否则跳出for循环。
因为code的两个属性的propKey没有被删除,因此跳出循环。
image.png

for (propKey in nextProps)

更新时,num的值为1,nextProps也有两个属性,一个是title,一个是children,因此值都为1
image.png
终止的情况是,nextProps不存在propKey)或者nextProp === lastProp或者nextProp == null && lastProp == null,争对的是更新或者新增的情况,否则跳出循环。
循环中,根据propKey的不同,进入不同的逻辑中。

  • title属性

由于title属性不属于这些属性,进入默认的处理逻辑。title处理完后,tilte属性会赋值给一个数组,数组第i项时propKey,第i+1项是对应的propsKey的value,即nextProp
image.png

  • children属性

image.png
diffProperties返回的数组,返回一个第i项是属性,第i+1项是值的数组。
image.png
prepareUpdate的数组返回给workInProgress.updateQueue。当updatePayload存在的情况下,进入markUpdate中。
image.png

markUpdate:将workInProgress的flags增加一个update的属性

  1. function markUpdate(workInProgress) {
  2. // Tag the fiber with an update effect. This turns a Placement into
  3. // a PlacementAndUpdate.
  4. workInProgress.flags |= Update;
  5. }

completeUnitOfWork:生成包含effectTag的链表

根据上图的call Stack中,执行完completeWork后进入performSyncWorkOnRoot中,进入commitRoot,最终进入commit阶段。

  1. function completeUnitOfWork(unitOfWork) {
  2. // Attempt to complete the current unit of work, then move to the next
  3. // sibling. If there are no more siblings, return to the parent fiber.
  4. var completedWork = unitOfWork;
  5. do {
  6. // The current, flushed, state of this fiber is the alternate. Ideally
  7. // nothing should rely on this, but relying on it here means that we don't
  8. // need an additional field on the work in progress.
  9. //当前节点
  10. var current = completedWork.alternate;
  11. //当前节点的父级 对应的FIber节点
  12. var returnFiber = completedWork.return; // Check if the work completed or if something threw.
  13. //如果当前完成节点的兄弟节点在执行“递”或“归”阶段抛出错误时,
  14. //表示这颗子树没有完成“递”与“归”的阶段,所以这个父节点就会被标记成Incomplete。
  15. //这些子树不包含Incomplete,表示这颗子树顺利完成了“递”与“归”的阶段。
  16. //此时需要将包含effectTag的节点挂载到returnFiber上(此处与视频源代码存在出处)
  17. if ((completedWork.flags & Incomplete) === NoFlags) {
  18. setCurrentFiber(completedWork);
  19. var next = void 0;
  20. if ( (completedWork.mode & ProfileMode) === NoMode) {
  21. next = completeWork(current, completedWork, subtreeRenderLanes);
  22. } else {
  23. startProfilerTimer(completedWork);
  24. next = completeWork(current, completedWork, subtreeRenderLanes); // Update render duration assuming we didn't error.
  25. stopProfilerTimerIfRunningAndRecordDelta(completedWork, false);
  26. }
  27. resetCurrentFiber();
  28. if (next !== null) {
  29. // Completing this fiber spawned new work. Work on that next.
  30. workInProgress = next;
  31. return;
  32. }
  33. } else {
  34. // This fiber did not complete because something threw. Pop values off
  35. // the stack without entering the complete phase. If this is a boundary,
  36. // capture values if possible.
  37. var _next = unwindWork(current, completedWork); // Because this fiber did not complete, don't reset its lanes.
  38. if (_next !== null) {
  39. // If completing this work spawned new work, do that next. We'll come
  40. // back here again.
  41. // Since we're restarting, remove anything that is not a host effect
  42. // from the effect tag.
  43. _next.flags &= HostEffectMask;
  44. workInProgress = _next;
  45. return;
  46. }
  47. if ( (completedWork.mode & ProfileMode) !== NoMode) {
  48. // Record the render duration for the fiber that errored.
  49. stopProfilerTimerIfRunningAndRecordDelta(completedWork, false); // Include the time spent working on failed children before continuing.
  50. var actualDuration = completedWork.actualDuration;
  51. var child = completedWork.child;
  52. while (child !== null) {
  53. actualDuration += child.actualDuration;
  54. child = child.sibling;
  55. }
  56. completedWork.actualDuration = actualDuration;
  57. }
  58. if (returnFiber !== null) {
  59. // Mark the parent fiber as incomplete and clear its subtree flags.
  60. returnFiber.flags |= Incomplete;
  61. returnFiber.subtreeFlags = NoFlags;
  62. returnFiber.deletions = null;
  63. } else {
  64. // We've unwound all the way to the root.
  65. workInProgressRootExitStatus = RootDidNotComplete;
  66. workInProgress = null;
  67. return;
  68. }
  69. }
  70. var siblingFiber = completedWork.sibling;
  71. if (siblingFiber !== null) {
  72. // If there is more work to do in this returnFiber, do that next.
  73. workInProgress = siblingFiber;
  74. return;
  75. } // Otherwise, return to the parent
  76. completedWork = returnFiber; // Update the next thing we're working on in case something throws.
  77. workInProgress = completedWork;
  78. } while (completedWork !== null); // We've reached the root.
  79. if (workInProgressRootExitStatus === RootInProgress) {
  80. workInProgressRootExitStatus = RootCompleted;
  81. }
  82. }

如果将RootFiber比喻一颗圣诞树,圣诞树上的灯表示一个FIber节点,每一次更新时,某些Fiber节点会标记上一些effectTag的标记,表示更新操作。

更新时是否需要像初始化渲染时,采用深度优先的方式,遍历整棵Fiber树,来找到包含effectTag的节点吗?

可行但效率低。如果一个节点包含Efag Tag,会与其他包含effectTag的Fiber节点,组成一个链表,就像图中连接红色灯的黄色灯带一样。当整个render阶段完成时,就形成一个连接所有Efag Tag的完整的链表。因此更新时,不需要像初次渲染时对root Fiber树广度遍历,只需遍历这条链表,就可以找到包含effectTag的节点。
image.png
returnFiber(当前节点的父级 对应的FIber节点)
image.png
completedWork表示当前完成的Fiber节点。
image.png
image.png

  1. //视频中此段代码
  2. if (returnFiber.firstEffect === null) {
  3. returnFiber.firstEffect = completedWork.firstEffect;
  4. }
  5. if (completedWork.lastEffect !== null) {
  6. if (returnFiber.lastEffect !== null) {
  7. returnFiber.lastEffect.nextEffect = completedWork.firstEffect;
  8. }
  9. returnFiber.lastEffect = completedWork.lastEffect;
  10. }
  11. const effectTag = completedWork.effectTag;
  12. if (effectTag > PerformedWork) {
  13. if (returnFiber.lastEffect !== null) {
  14. returnFiber.lastEffect.nextEffect = completedWork;
  15. } else {
  16. returnFiber.firstEffect = completedWork;
  17. }
  18. returnFiber.lastEffect = completedWork;
  19. }

p标签不为null,且p标签的effectTag按位与Incomplete的值不等于NoFlags。如果当前完成节点的兄弟节点在执行“递”或“归”阶段抛出错误时,表示这颗子树没有完成“递”与“归”的阶段,所以这个父节点就会被标记成Incomplete。这些子树不包含Incomplete,表示这颗子树顺利完成了“递”与“归”的阶段,此时需要将包含effectTag的节点挂载到EffectTag的链表上。

1.code

第一个进入completeUnitOfWork的Firber节点是code,code的firstEffct为null,继续往下,判断当前completeWork是否包含effectTag(code的Tiltle和children发生了变化),包含进入下面returnFiber.firstEffect=completedWork.firstEffect,returnFiber.lastEffect=completedWork.lastEffect,即p的firstEffct赋值给当前的code,p的lastEffect赋值给当前的code。

2.p

下一个是p,p的returnFiber是header,header的firstEffect为null,则returnFiber.firstEffect=completedWork.firstEffect,即header的firstEffect赋值给p。继续,completedWork.lastEffect !== null,当前节点的lastEffect不等于null,继续,
header.lastEffect为null,returnFiber.lastEffect = completedWork.lastEffect,header的lastEffect等于p的lastEffect,继续
image.png
p的effect是否包含Effect,原因?
因为P的props有一个箭头函数onClick,接受一个箭头函数,所以每一次render时,他的onClick的值都是不同的箭头函数,所以p的属性都有变化,所以包含Effct

  1. ....
  2. const handleNum = () => {
  3. let _num = num + 1
  4. setNum(_num)
  5. }
  6. <p onClick={handleNum}>
  7. <code title={num}>{num}</code>
  8. </p>
  9. .....

header的lastEffect不为null,将header的lastEffct的nextEffect赋值给p,header的lastEffect赋值给p
image.png

3.a

下一个是a,header的firstEffect不为null,跳过;a的lastEffect等于null,跳过;a的内容不变,不包含effct,跳过。

4.header

div的firstEffect为null,div的firstEffect等于header的firstEffect;header的lastEffect不等于null,div的lastEffect等于null。div的lastEffect等于header的lastEffect(即p)。header不包含effect。
image.png
由此可以看出,整个流程中,当到达根节点,根节点的firstEffect指向的是第一个包含effect的叶子节点,此处为code,而code的nextEffect指向的是P。在本次更新中,只有code与p存在effctTag。如果有其他fiber节点的effct存在,p的nectEffect又指向的是下一个含Effect的Fiber 节点。
image.png
finishWork存在firstEffect,他的firstEffect是code,code的nextEffect是p,p的nectEffect为null。
所以本次更新只有code和p包含effect,
结合,这就是render 阶段的完整流程。
completeWork.png