img节点
completeUnitOfWork
image.png
image.png
image.png

  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. var current = completedWork.alternate;
  10. var returnFiber = completedWork.return; // Check if the work completed or if something threw.
  11. if ((completedWork.flags & Incomplete) === NoFlags) {
  12. setCurrentFiber(completedWork);
  13. var next = void 0;
  14. if ( (completedWork.mode & ProfileMode) === NoMode) {
  15. next = completeWork(current, completedWork, subtreeRenderLanes);
  16. } else {
  17. startProfilerTimer(completedWork);
  18. next = completeWork(current, completedWork, subtreeRenderLanes); // Update render duration assuming we didn't error.
  19. stopProfilerTimerIfRunningAndRecordDelta(completedWork, false);
  20. }
  21. resetCurrentFiber();
  22. //img的子节点没null跳过
  23. if (next !== null) {
  24. // Completing this fiber spawned new work. Work on that next.
  25. workInProgress = next;
  26. return;
  27. }
  28. } else {
  29. // This fiber did not complete because something threw. Pop values off
  30. // the stack without entering the complete phase. If this is a boundary,
  31. // capture values if possible.
  32. var _next = unwindWork(current, completedWork); // Because this fiber did not complete, don't reset its lanes.
  33. if (_next !== null) {
  34. // If completing this work spawned new work, do that next. We'll come
  35. // back here again.
  36. // Since we're restarting, remove anything that is not a host effect
  37. // from the effect tag.
  38. _next.flags &= HostEffectMask;
  39. workInProgress = _next;
  40. return;
  41. }
  42. if ( (completedWork.mode & ProfileMode) !== NoMode) {
  43. // Record the render duration for the fiber that errored.
  44. stopProfilerTimerIfRunningAndRecordDelta(completedWork, false); // Include the time spent working on failed children before continuing.
  45. var actualDuration = completedWork.actualDuration;
  46. var child = completedWork.child;
  47. while (child !== null) {
  48. actualDuration += child.actualDuration;
  49. child = child.sibling;
  50. }
  51. completedWork.actualDuration = actualDuration;
  52. }
  53. if (returnFiber !== null) {
  54. // Mark the parent fiber as incomplete and clear its subtree flags.
  55. returnFiber.flags |= Incomplete;
  56. returnFiber.subtreeFlags = NoFlags;
  57. returnFiber.deletions = null;
  58. } else {
  59. // We've unwound all the way to the root.
  60. workInProgressRootExitStatus = RootDidNotComplete;
  61. workInProgress = null;
  62. return;
  63. }
  64. }
  65. //没有子节点,进入兄弟节点
  66. var siblingFiber = completedWork.sibling;
  67. if (siblingFiber !== null) {
  68. // If there is more work to do in this returnFiber, do that next.
  69. workInProgress = siblingFiber;
  70. return;
  71. } // Otherwise, return to the parent
  72. completedWork = returnFiber; // Update the next thing we're working on in case something throws.
  73. workInProgress = completedWork;
  74. //循环completedWork,直到root
  75. } while (completedWork !== null); // We've reached the root.
  76. if (workInProgressRootExitStatus === RootInProgress) {
  77. workInProgressRootExitStatus = RootCompleted;
  78. }
  79. }

第一个进入completeWork的是img节点,且tag为5,根据fiber节点的tag进入不同的case,进入HostComponent
image.png
首先判断current是否存在,首屏渲染中,current为null
image.png
_wasHydrated与srr相关,当前为false
image.png

completeWork

  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. //当前img节点的tag是5,进入hostComponent
  8. switch (workInProgress.tag) {
  9. case IndeterminateComponent:
  10. //对于FunctionComponent等其他组件没有completeWork
  11. case LazyComponent:
  12. case SimpleMemoComponent:
  13. case FunctionComponent:
  14. case ForwardRef:
  15. case Fragment:
  16. case Mode:
  17. case Profiler:
  18. case ContextConsumer:
  19. case MemoComponent:
  20. bubbleProperties(workInProgress);
  21. return null;
  22. ...
  23. case HostComponent:
  24. {
  25. popHostContext(workInProgress);
  26. var rootContainerInstance = getRootHostContainer();
  27. var type = workInProgress.type;
  28. //判断current是否存在,首屏渲染时,current不存在
  29. if (current !== null && workInProgress.stateNode != null) {
  30. updateHostComponent$1(current, workInProgress, type, newProps, rootContainerInstance);
  31. if (current.ref !== workInProgress.ref) {
  32. markRef$1(workInProgress);
  33. }
  34. } else {
  35. if (!newProps) { //当前组件获得的属性,例如img的src、className等内容
  36. if (workInProgress.stateNode === null) {
  37. 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.');
  38. } // This can happen when we abort work.
  39. bubbleProperties(workInProgress);
  40. return null;
  41. }
  42. var currentHostContext = getHostContext(); // TODO: Move createInstance to beginWork and keep it on a context
  43. // "stack" as the parent. Then append children as we go in beginWork
  44. // or completeWork depending on whether we want to add them top->down or
  45. // bottom->up. Top->down is faster in IE11.
  46. //与srr相关,当前为false
  47. var _wasHydrated = popHydrationState(workInProgress);
  48. if (_wasHydrated) {
  49. // TODO: Move this and createInstance step into the beginPhase
  50. // to consolidate.
  51. if (prepareToHydrateHostInstance(workInProgress, rootContainerInstance, currentHostContext)) {
  52. // If changes to the hydrated node need to be applied at the
  53. // commit-phase we mark this as such.
  54. markUpdate(workInProgress);
  55. }
  56. } else {
  57. //进入createInstance 为HostComponent节点创建对应的dom节点
  58. var instance = createInstance(type, newProps, rootContainerInstance, currentHostContext, workInProgress);
  59. //将创建好的dom节点,插入到已经创建好的dom树中
  60. appendAllChildren(instance, workInProgress, false, false);
  61. workInProgress.stateNode = instance; // Certain renderers require commit-time effects for initial mount.
  62. // (eg DOM renderer supports auto-focus for certain elements).
  63. // Make sure such renderers get scheduled for later work.
  64. if (finalizeInitialChildren(instance, type, newProps, rootContainerInstance)) {
  65. markUpdate(workInProgress);
  66. }
  67. }
  68. if (workInProgress.ref !== null) {
  69. // If there is a ref on a host node we need to schedule a callback
  70. markRef$1(workInProgress);
  71. }
  72. }
  73. bubbleProperties(workInProgress);
  74. return null;
  75. }
  76. ...
  77. throw new Error("Unknown unit of work tag (" + workInProgress.tag + "). This error is likely caused by a bug in " + 'React. Please file an issue.');
  78. }

createInstance -创建对应的dom节点

为hostComponent创建对应的dom节点

image.png

createElement-createInstance方法中的createElement生成img的dom元素

image.png

  1. function createInstance(type, props, rootContainerInstance, hostContext, internalInstanceHandle) {
  2. var parentNamespace = void 0
  3. ...
  4. //生成当前dom节点的dom元素
  5. var domElement = createElement(type, props, rootContainerInstance, parentNamespace)
  6. precacheFiberNode(internalInstanceHandle, domElement)
  7. updateFiberProps(domElement, props)
  8. return domElement
  9. }

appendAllChildren-将createInstance创建的dom节点插入到已经创建好的dom数中
由于img是第一个dom节点,会跳过此次操作。

  1. {
  2. // // 将子孙DOM节点插入刚生成的DOM节点中
  3. appendAllChildren = function (parent, workInProgress, needsVisibilityToggle, isHidden) {
  4. // We only have the top Fiber that was created but we need recurse down its
  5. // children to find all the terminal nodes.
  6. var node = workInProgress.child;
  7. while (node !== null) {
  8. if (node.tag === HostComponent || node.tag === HostText) {
  9. appendInitialChild(parent, node.stateNode);
  10. } else if (node.tag === HostPortal) ; else if (node.child !== null) {
  11. node.child.return = node;
  12. node = node.child;
  13. continue;
  14. }
  15. if (node === workInProgress) {
  16. return;
  17. }
  18. while (node.sibling === null) {
  19. if (node.return === null || node.return === workInProgress) {
  20. return;
  21. }
  22. node = node.return;
  23. }
  24. node.sibling.return = node.return;
  25. node = node.sibling;
  26. }
  27. };

workInProgress.stateNode=instance

将imgdom节点赋值给workInProgress.stateNode上面

finalizeInitialChildren-为dom节点设置一些属性

image.png

  1. function finalizeInitialChildren(domElement, type, props, rootContainerInstance, hostContext) {
  2. //设置初始化属性的操作
  3. setInitialProperties(domElement, type, props, rootContainerInstance);
  4. switch (type) {
  5. case 'button':
  6. case 'input':
  7. case 'select':
  8. case 'textarea':
  9. return !!props.autoFocus;
  10. case 'img':
  11. return true;
  12. default:
  13. return false;
  14. }
  15. }

setInitialProperties-设置初始化属性
image.png

  1. function setInitialProperties(domElement, tag, rawProps, rootContainerElement) {
  2. //是否是自定义的标签
  3. var isCustomComponentTag = isCustomComponent(tag, rawProps);
  4. {
  5. validatePropertiesInDevelopment(tag, rawProps);
  6. } // TODO: Make sure that we check isMounted before firing any of these events.
  7. var props;
  8. // 根据tag进入不同的标签
  9. switch (tag) {
  10. ...
  11. case 'img':
  12. case 'image':
  13. case 'link':
  14. // We listen to these events in case to ensure emulated bubble
  15. // listeners still fire for error and load events.
  16. listenToNonDelegatedEvent('error', domElement);
  17. listenToNonDelegatedEvent('load', domElement);
  18. props = rawProps;
  19. ...
  20. default:
  21. props = rawProps;
  22. }
  23. //判断props是否合法
  24. assertValidProps(tag, props);
  25. //初始化dom属性的操作
  26. setInitialDOMProperties(tag, domElement, rootContainerElement, props, isCustomComponentTag);
  27. switch (tag) {
  28. case 'input':
  29. // TODO:确保我们检查这是否仍处于卸载状态或进行任何清理
  30. // up necessary since we never stop tracking anymore.
  31. track(domElement);
  32. postMountWrapper(domElement, rawProps, false);
  33. break;
  34. case 'textarea':
  35. // TODO: Make sure we check if this is still unmounted or do any clean
  36. // up necessary since we never stop tracking anymore.
  37. track(domElement);
  38. postMountWrapper$3(domElement);
  39. break;
  40. case 'option':
  41. postMountWrapper$1(domElement, rawProps);
  42. break;
  43. case 'select':
  44. postMountWrapper$2(domElement, rawProps);
  45. break;
  46. default:
  47. if (typeof props.onClick === 'function') {
  48. // TODO: This cast may not be sound for SVG, MathML or custom elements.
  49. trapClickOnNonInteractiveElement(domElement);
  50. }
  51. break;
  52. }
  53. } // Calculate the diff between the two objects.

setInitialDOMProperties-初始化dom元素的值image.png
image.png

  1. function setInitialDOMProperties(tag, domElement, rootContainerElement, nextProps, isCustomComponentTag) {
  2. for (var propKey in nextProps) { //遍历nextProps,通过nextProps给相关dom属性赋值
  3. if (!nextProps.hasOwnProperty(propKey)) {
  4. continue;
  5. }
  6. var nextProp = nextProps[propKey];
  7. if (propKey === STYLE) {//STYLE =>'style'属性
  8. {
  9. if (nextProp) {
  10. // Freeze the next style object so that we can assume it won't be
  11. // mutated. We have already warned for this in the past.
  12. Object.freeze(nextProp);
  13. }
  14. } // Relies on `updateStylesByID` not mutating `styleUpdates`.
  15. setValueForStyles(domElement, nextProp);
  16. } else if (propKey === DANGEROUSLY_SET_INNER_HTML) {
  17. var nextHtml = nextProp ? nextProp[HTML$1] : undefined;
  18. if (nextHtml != null) {
  19. setInnerHTML(domElement, nextHtml);
  20. }
  21. } else if (propKey === CHILDREN) {
  22. if (typeof nextProp === 'string') {
  23. // Avoid setting initial textContent when the text is empty. In IE11 setting
  24. // textContent on a <textarea> will cause the placeholder to not
  25. // show within the <textarea> until it has been focused and blurred again.
  26. // https://github.com/facebook/react/issues/6731#issuecomment-254874553
  27. var canSetTextContent = tag !== 'textarea' || nextProp !== '';
  28. if (canSetTextContent) {
  29. setTextContent(domElement, nextProp);
  30. }
  31. } else if (typeof nextProp === 'number') {
  32. setTextContent(domElement, '' + nextProp);
  33. }
  34. } else if (propKey === SUPPRESS_CONTENT_EDITABLE_WARNING || propKey === SUPPRESS_HYDRATION_WARNING) ; else if (propKey === AUTOFOCUS) ; else if (registrationNameDependencies.hasOwnProperty(propKey)) {
  35. if (nextProp != null) {
  36. if ( typeof nextProp !== 'function') {
  37. warnForInvalidEventListener(propKey, nextProp);
  38. }
  39. if (propKey === 'onScroll') {
  40. listenToNonDelegatedEvent('scroll', domElement);
  41. }
  42. }
  43. } else if (nextProp != null) {
  44. setValueForProperty(domElement, propKey, nextProp, isCustomComponentTag);
  45. }
  46. }
  47. }

setValueForProperty-使用setAttribute给dom节点的属性赋值
image.png
image.png

  1. function setValueForProperty(node, name, value, isCustomComponentTag) {
  2. ..
  3. if (isCustomComponentTag || propertyInfo === null) {
  4. if (isAttributeNameSafe(name)) {
  5. var _attributeName = name;
  6. if (value === null) {
  7. node.removeAttribute(_attributeName);
  8. } else {
  9. {
  10. checkAttributeStringCoercion(value, name);
  11. }
  12. node.setAttribute(_attributeName, '' + value);
  13. }
  14. }
  15. return;
  16. }
  17. ...
  18. var attributeName = propertyInfo.attributeName,
  19. attributeNamespace = propertyInfo.attributeNamespace;
  20. if (value === null) {
  21. node.removeAttribute(attributeName);
  22. } else {
  23. var _type = propertyInfo.type;
  24. var attributeValue;
  25. if (_type === BOOLEAN || _type === OVERLOADED_BOOLEAN && value === true) {
  26. // If attribute type is boolean, we know for sure it won't be an execution sink
  27. // and we won't require Trusted Type here.
  28. attributeValue = '';
  29. } else {
  30. // `setAttribute` with objects becomes only `[object]` in IE8/9,
  31. // ('' + value) makes it output the correct toString()-value.
  32. {
  33. {
  34. checkAttributeStringCoercion(value, attributeName);
  35. }
  36. attributeValue = '' + value;
  37. }
  38. if (propertyInfo.sanitizeURL) {
  39. sanitizeURL(attributeValue.toString());
  40. }
  41. }
  42. if (attributeNamespace) {
  43. node.setAttributeNS(attributeNamespace, attributeName, attributeValue);
  44. } else {
  45. //给dom属性赋值
  46. node.setAttribute(attributeName, attributeValue);
  47. }
  48. }
  49. }

markUpdate-更新

image.png

最终,完成一个fiber节点的completeWork完成。

下一个进入completeWork的是edit文本-HostText
image.png

为HostText-创建文本节点

  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. //当前Edit节点的tag是6,进入HostText
  8. switch (workInProgress.tag) {
  9. ...
  10. case HostText:
  11. {
  12. var newText = newProps;
  13. //mount阶段 current为null
  14. if (current && workInProgress.stateNode != null) {
  15. var oldText = current.memoizedProps; // If we have an alternate, that means this is an update and we need
  16. // to schedule a side-effect to do the updates.
  17. updateHostText$1(current, workInProgress, oldText, newText);
  18. } else {
  19. if (typeof newText !== 'string') {
  20. if (workInProgress.stateNode === null) {
  21. 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.');
  22. } // This can happen when we abort work.
  23. }
  24. //获取项目根据节点,当前为#root
  25. var _rootContainerInstance = getRootHostContainer();
  26. //获取当前上下文
  27. var _currentHostContext = getHostContext();
  28. var _wasHydrated2 = popHydrationState(workInProgress);
  29. if (_wasHydrated2) {
  30. if (prepareToHydrateHostTextInstance(workInProgress)) {
  31. markUpdate(workInProgress);
  32. }
  33. } else {
  34. //因为是文本节点-创建dom节点
  35. workInProgress.stateNode = createTextInstance(newText, _rootContainerInstance, _currentHostContext, workInProgress);
  36. }
  37. }
  38. bubbleProperties(workInProgress);
  39. return null;
  40. }
  41. ...
  42. }

createTextInstance

  1. function createTextInstance(text, rootContainerInstance, hostContext, internalInstanceHandle) {
  2. {
  3. var hostContextDev = hostContext;
  4. //检查dom元素是否有效
  5. validateDOMNesting(null, text, hostContextDev.ancestorInfo);
  6. }
  7. //生成文本节点
  8. var textNode = createTextNode(text, rootContainerInstance);
  9. precacheFiberNode(internalInstanceHandle, textNode);
  10. return textNode;
  11. }

createTextNode-为文本节点设置属性
image.png
image.png
precacheFiberNode-生成一个Fiber节点,并当completeWorkp标签的appendAllChildren追加。

  1. function precacheFiberNode(hostInst, node) {
  2. node[internalInstanceKey] = hostInst;
  3. }

最终,完成一个文本fiber节点的completeWork完成。

下一个进入文本节点是src/App.js
下一个是code
下一个是and save to reload
下一个是p,
下一个是 Learn React
下一个是a
下一个是header
下一个是div className=’App’
下一个是div className=’root’
最终生成一个Fiber树
由于completeWork属于“归”阶段调用的函数,每次调用appendAllChildren时都会将已生成的子孙DOM节点插入当前生成的DOM节点下。那么当“归”到rootFiber时,我们已经有一个构建好的离屏DOM树。

首屏渲染,dom节点如何挂载到页面中?

image.png
首屏渲染时,只有一个fiber节点,就是div #root根fiber节点

  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. }
  1. function reconcileChildFibers(returnFiber, currentFirstChild, newChild, lanes) {
  2. var isUnkeyedTopLevelFragment = typeof newChild === 'object' && newChild !== null && newChild.type === REACT_FRAGMENT_TYPE && newChild.key === null;
  3. if (isUnkeyedTopLevelFragment) {
  4. newChild = newChild.props.children;
  5. } // Handle object types
  6. if (typeof newChild === 'object' && newChild !== null) {
  7. switch (newChild.$$typeof) {
  8. case REACT_ELEMENT_TYPE:
  9. return placeSingleChild(reconcileSingleElement(returnFiber, currentFirstChild, newChild, lanes));
  10. ...
  11. return reconcileChildFibers(returnFiber, currentFirstChild, init(payload), lanes);
  12. }
  13. ...
  14. return deleteRemainingChildren(returnFiber, currentFirstChild);
  15. }
  16. return reconcileChildFibers;
  17. }
  1. function placeSingleChild(newFiber) {
  2. // This is simpler for the single child case. We only need to do a
  3. // placement for inserting new children.
  4. if (shouldTrackSideEffects && newFiber.alternate === null) {
  5. newFiber.flags |= Placement;
  6. }
  7. return

首次渲染阶段,fiber.effctTag为Placement。