render方法解析

要将react元素渲染到页面当中,分为了两个阶段: render阶段 和 commit阶段。

  • render阶段:由协调层负责的阶段

在这个阶段当中要为每一个react元素构建对应的fiber对象,在构建fiber对象的过程当中还要为此fiber对象构建其对应的DOM对象。并且还要为fiber对象添加effectTag属性,用于标注当前fiber对象要对应的DOM对象要进行什么样的操作,是插入、删除、还是更新。这个新构建的fiber对象我们称之为workInProgress Fiber树,也可以将其理解为待提交fiber树(此时仅在内存中作用,尚未渲染到页面成为真实DOM)。当render阶段结束之后, 它会被保存在fiberRoot对象当中。接下来就可以进入到commit阶段了。

  • commit阶段:

在commit阶段当中,会先获得render阶段的工作成果,也就是获取到保存在fiberRoot对象当中的新构建的workInProgress Fiber树。接下来就是根据fiber对象当中的effectTag属性进行相应的DOM操作。

源码分析

render方法是在react源码中的:src/react/packages/react-dom/index.js位置。

export * from './src/client/ReactDOM';

它主要是从src/react/packages/react-dom/src/client/ReactDOM.js中导出的内容。

import type {ReactNodeList} from 'shared/ReactTypes';
import type {Container} from './ReactDOMHostConfig';import '../shared/checkReact';
import './ReactDOMClientInjection';
import {findDOMNode,render,hydrate,unstable_renderSubtreeIntoContainer,unmountComponentAtNode,
} from './ReactDOMLegacy';
import {createRoot, createBlockingRoot, isValidContainer} from './ReactDOMRoot';import {batchedEventUpdates,batchedUpdates,discreteUpdates,flushDiscreteUpdates,flushSync,flushControlled,injectIntoDevTools,flushPassiveEffects,IsThisRendererActing,attemptSynchronousHydration,attemptUserBlockingHydration,attemptContinuousHydration,attemptHydrationAtCurrentPriority,
} from 'react-reconciler/inline.dom';
import {createPortal as createPortalImpl} from 'shared/ReactPortal';
import {canUseDOM} from 'shared/ExecutionEnvironment';
import {setBatchingImplementation} from 'legacy-events/ReactGenericBatching';
import {setRestoreImplementation,enqueueStateRestore,restoreStateIfNeeded,
} from 'legacy-events/ReactControlledComponent';
import {runEventsInBatch} from 'legacy-events/EventBatching';
import {eventNameDispatchConfigs,injectEventPluginsByName,
} from 'legacy-events/EventPluginRegistry';
import {accumulateTwoPhaseDispatches,accumulateDirectDispatches,
} from 'legacy-events/EventPropagators';
import ReactVersion from 'shared/ReactVersion';
import invariant from 'shared/invariant';
import {warnUnstableRenderSubtreeIntoContainer} from 'shared/ReactFeatureFlags';import {getInstanceFromNode,getNodeFromInstance,getFiberCurrentPropsFromNode,getClosestInstanceFromNode,
} from './ReactDOMComponentTree';
import {restoreControlledState} from './ReactDOMComponent';
import {dispatchEvent} from '../events/ReactDOMEventListener';
import {setAttemptSynchronousHydration,setAttemptUserBlockingHydration,setAttemptContinuousHydration,setAttemptHydrationAtCurrentPriority,queueExplicitHydrationTarget,
} from '../events/ReactDOMEventReplaying';setAttemptSynchronousHydration(attemptSynchronousHydration);
setAttemptUserBlockingHydration(attemptUserBlockingHydration);
setAttemptContinuousHydration(attemptContinuousHydration);
setAttemptHydrationAtCurrentPriority(attemptHydrationAtCurrentPriority);let didWarnAboutUnstableCreatePortal = false;
let didWarnAboutUnstableRenderSubtreeIntoContainer = false;if (__DEV__) {if (typeof Map !== 'function' ||// $FlowIssue Flow incorrectly thinks Map has no prototypeMap.prototype == null ||typeof Map.prototype.forEach !== 'function' ||typeof Set !== 'function' ||// $FlowIssue Flow incorrectly thinks Set has no prototypeSet.prototype == null ||typeof Set.prototype.clear !== 'function' ||typeof Set.prototype.forEach !== 'function') {console.error('React depends on Map and Set built-in types. Make sure that you load a ' +'polyfill in older browsers. https://fb.me/react-polyfills',);}
}setRestoreImplementation(restoreControlledState);
setBatchingImplementation(batchedUpdates,discreteUpdates,flushDiscreteUpdates,batchedEventUpdates,
);function createPortal(children: ReactNodeList,container: Container,key: ?string = null,
): React$Portal {invariant(isValidContainer(container),'Target container is not a DOM element.',);// TODO: pass ReactDOM portal implementation as third argument// $FlowFixMe The Flow type is opaque but there's no way to actually create it.return createPortalImpl(children, container, null, key);
}function scheduleHydration(target: Node) {if (target) {queueExplicitHydrationTarget(target);}
}function renderSubtreeIntoContainer(parentComponent: React$Component<any, any>,element: React$Element<any>,containerNode: Container,callback: ?Function,
) {if (__DEV__) {if (warnUnstableRenderSubtreeIntoContainer &&!didWarnAboutUnstableRenderSubtreeIntoContainer) {didWarnAboutUnstableRenderSubtreeIntoContainer = true;console.warn('ReactDOM.unstable_renderSubtreeIntoContainer() is deprecated ' +'and will be removed in a future major release. Consider using ' +'React Portals instead.',);}}return unstable_renderSubtreeIntoContainer(parentComponent,element,containerNode,callback,);
}function unstable_createPortal(children: ReactNodeList,container: Container,key: ?string = null,
) {if (__DEV__) {if (!didWarnAboutUnstableCreatePortal) {didWarnAboutUnstableCreatePortal = true;console.warn('The ReactDOM.unstable_createPortal() alias has been deprecated, ' +'and will be removed in React 17+. Update your code to use ' +'ReactDOM.createPortal() instead. It has the exact same API, ' +'but without the "unstable_" prefix.',);}}return createPortal(children, container, key);
}const Internals = {// Keep in sync with ReactDOMUnstableNativeDependencies.js// ReactTestUtils.js, and ReactTestUtilsAct.js. This is an array for better minification.Events: [getInstanceFromNode,getNodeFromInstance,getFiberCurrentPropsFromNode,injectEventPluginsByName,eventNameDispatchConfigs,accumulateTwoPhaseDispatches,accumulateDirectDispatches,enqueueStateRestore,restoreStateIfNeeded,dispatchEvent,runEventsInBatch,flushPassiveEffects,IsThisRendererActing,],
};export {createPortal,batchedUpdates as unstable_batchedUpdates,flushSync,Internals as __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED,ReactVersion as version,// Disabled behind disableLegacyReactDOMAPIsfindDOMNode,hydrate,render,unmountComponentAtNode,// exposeConcurrentModeAPIscreateRoot,createBlockingRoot,discreteUpdates as unstable_discreteUpdates,flushDiscreteUpdates as unstable_flushDiscreteUpdates,flushControlled as unstable_flushControlled,scheduleHydration as unstable_scheduleHydration,// Disabled behind disableUnstableRenderSubtreeIntoContainerrenderSubtreeIntoContainer as unstable_renderSubtreeIntoContainer,// Disabled behind disableUnstableCreatePortal// Temporary alias since we already shipped React 16 RC with it.// TODO: remove in React 17.unstable_createPortal,
};const foundDevTools = injectIntoDevTools({findFiberByHostInstance: getClosestInstanceFromNode,bundleType: __DEV__ ? 1 : 0,version: ReactVersion,rendererPackageName: 'react-dom',
});if (__DEV__) {if (!foundDevTools && canUseDOM && window.top === window.self) {// If we're in Chrome or Firefox, provide a download link if not installed.if ((navigator.userAgent.indexOf('Chrome') > -1 &&navigator.userAgent.indexOf('Edge') === -1) ||navigator.userAgent.indexOf('Firefox') > -1) {const protocol = window.location.protocol;// Don't warn in exotic cases like chrome-extension://.if (/^(https?|file):$/.test(protocol)) {// eslint-disable-next-line react-internal/no-production-loggingconsole.info('%cDownload the React DevTools ' +'for a better development experience: ' +'https://fb.me/react-devtools' +(protocol === 'file:'? '\nYou might need to use a local HTTP server (instead of file://): ' +'https://fb.me/react-devtools-faq': ''),'font-weight:bold',);}}}
}

在这里面我们可以看到render方法就是从这里被导出的。而它真正被定义的位置是在:src/react/packages/react-dom/src/client/ReactDOMLegacy.js。


import type {Container} from './ReactDOMHostConfig';
import type {RootType} from './ReactDOMRoot';
import type {ReactNodeList} from 'shared/ReactTypes';import {getInstanceFromNode,isContainerMarkedAsRoot,unmarkContainerAsRoot,
} from './ReactDOMComponentTree';
import {createLegacyRoot, isValidContainer} from './ReactDOMRoot';
import {ROOT_ATTRIBUTE_NAME} from '../shared/DOMProperty';
import {DOCUMENT_NODE,ELEMENT_NODE,COMMENT_NODE,
} from '../shared/HTMLNodeType';import {findHostInstanceWithNoPortals,updateContainer,unbatchedUpdates,getPublicRootInstance,findHostInstance,findHostInstanceWithWarning,
} from 'react-reconciler/inline.dom';
import getComponentName from 'shared/getComponentName';
import invariant from 'shared/invariant';
import ReactSharedInternals from 'shared/ReactSharedInternals';
import {has as hasInstance} from 'shared/ReactInstanceMap';const ReactCurrentOwner = ReactSharedInternals.ReactCurrentOwner;let topLevelUpdateWarnings;
let warnedAboutHydrateAPI = false;if (__DEV__) {topLevelUpdateWarnings = (container: Container) => {if (container._reactRootContainer && container.nodeType !== COMMENT_NODE) {const hostInstance = findHostInstanceWithNoPortals(container._reactRootContainer._internalRoot.current,);if (hostInstance) {if (hostInstance.parentNode !== container) {console.error('render(...): It looks like the React-rendered content of this ' +'container was removed without using React. This is not ' +'supported and will cause errors. Instead, call ' +'ReactDOM.unmountComponentAtNode to empty a container.',);}}}const isRootRenderedBySomeReact = !!container._reactRootContainer;const rootEl = getReactRootElementInContainer(container);const hasNonRootReactChild = !!(rootEl && getInstanceFromNode(rootEl));if (hasNonRootReactChild && !isRootRenderedBySomeReact) {console.error('render(...): Replacing React-rendered children with a new root ' +'component. If you intended to update the children of this node, ' +'you should instead have the existing children update their state ' +'and render the new components instead of calling ReactDOM.render.',);}if (container.nodeType === ELEMENT_NODE &&((container: any): Element).tagName &&((container: any): Element).tagName.toUpperCase() === 'BODY') {console.error('render(): Rendering components directly into document.body is ' +'discouraged, since its children are often manipulated by third-party ' +'scripts and browser extensions. This may lead to subtle ' +'reconciliation issues. Try rendering into a container element created ' +'for your app.',);}};
}function getReactRootElementInContainer(container: any) {if (!container) {return null;}if (container.nodeType === DOCUMENT_NODE) {return container.documentElement;} else {return container.firstChild;}
}function shouldHydrateDueToLegacyHeuristic(container) {const rootElement = getReactRootElementInContainer(container);return !!(rootElement &&rootElement.nodeType === ELEMENT_NODE &&rootElement.hasAttribute(ROOT_ATTRIBUTE_NAME));
}/*** 判断是否为服务器端渲染 如果不是服务器端渲染* 清空 container 容器中的节点*/
function legacyCreateRootFromDOMContainer(container: Container,forceHydrate: boolean,
): RootType {// container => <div id="root"></div>// 检测是否为服务器端渲染const shouldHydrate =forceHydrate || shouldHydrateDueToLegacyHeuristic(container);// 如果不是服务器端渲染if (!shouldHydrate) {let warned = false;let rootSibling;// 开启循环 删除 container 容器中的节点while ((rootSibling = container.lastChild)) {// 在开发环境中if (__DEV__) {/*** 判断子节点是否为元素节点, 并且元素节点不能有 data-reactroot 属性, 否则报错* 通常在服务器端渲染时会遇到这个问题** <div id="app">*  <%- markup %>* </div>* ↑ 报错** <div id="app"><%- markup %></div>* ↑ 不报错 删除了所有空白区域*/if (!warned &&rootSibling.nodeType === ELEMENT_NODE &&// 判断是否是服务端渲染的标志(rootSibling: any).hasAttribute(ROOT_ATTRIBUTE_NAME)) {warned = true;console.error('render(): Target node has markup rendered by React, but there ' +'are unrelated nodes as well. This is most commonly caused by ' +'white-space inserted around server-rendered markup.',);}}// 删除 container 容器中的节点container.removeChild(rootSibling);/*** 为什么要清除 container 中的元素 ?* 有时需要在 container 中放置一些占位图或者 loading 图以提高首屏加载用户体验,* 就无可避免的要向 container 中加入 html 标记.* 在将 ReactElement 渲染到 container 之前, 必然要先清空 container* 因为占位图和 ReactElement 不能同时显示** 在加入占位代码时, 最好只有一个父级元素, 可以减少内部代码的循环次数以提高性能* <div>*  <p>placement<p>*  <p>placement<p>*  <p>placement<p>* </div>*/}}// 在开发环境下if (__DEV__) {// 如果是服务器端渲染 控制台提示错误if (shouldHydrate && !forceHydrate && !warnedAboutHydrateAPI) {warnedAboutHydrateAPI = true;// 在客户端二次渲染时不要使用 render 方法, 要使用 hydrate 方法替代console.warn('render(): Calling ReactDOM.render() to hydrate server-rendered markup ' +'will stop working in React v17. Replace the ReactDOM.render() call ' +'with ReactDOM.hydrate() if you want React to attach to the server HTML.',);}}return createLegacyRoot(container,shouldHydrate? {hydrate: true,}: undefined,);
}function warnOnInvalidCallback(callback: mixed, callerName: string): void {if (__DEV__) {if (callback !== null && typeof callback !== 'function') {console.error('%s(...): Expected the last optional `callback` argument to be a ' +'function. Instead received: %s.',callerName,callback,);}}
}
/*** 将子树渲染到容器中 (初始化 Fiber 数据结构: 创建 fiberRoot 及 rootFiber)* parentComponent: 父组件, 初始渲染传入了 null* children: render 方法中的第一个参数, 要渲染的 ReactElement* container: 渲染容器* forceHydrate: true 为服务端渲染, false 为客户端渲染* callback: 组件渲染完成后需要执行的回调函数**/
function legacyRenderSubtreeIntoContainer(parentComponent: ?React$Component<any, any>,children: ReactNodeList,container: Container,forceHydrate: boolean,callback: ?Function,
) {if (__DEV__) {topLevelUpdateWarnings(container);warnOnInvalidCallback(callback === undefined ? null : callback, 'render');}/*** 检测 container 是否已经是初始化过的渲染容器* react 在初始渲染时会为最外层容器添加 _reactRootContainer 属性* react 会根据此属性进行不同的渲染方式* root 不存在 表示初始渲染* root 存在 表示更新*/// 获取 container 容器对象下是否有 _reactRootContainer 属性let root: RootType = (container._reactRootContainer: any);// 即将存储根 Fiber 对象let fiberRoot;if (!root) {// 初始渲染// 初始化根 Fiber 数据结构// 为 container 容器添加 _reactRootContainer 属性// 在 _reactRootContainer 对象中有一个属性叫做 _internalRoot// _internalRoot 属性值即为 FiberRoot 表示根节点 Fiber 数据结构// legacyCreateRootFromDOMContainer// createLegacyRoot// new ReactDOMBlockingRoot -> this._internalRoot// createRootImplroot = container._reactRootContainer = legacyCreateRootFromDOMContainer(container,forceHydrate,);// 获取 Fiber Root 对象fiberRoot = root._internalRoot;/*** 改变 callback 函数中的 this 指向* 使其指向 render 方法第一个参数的真实 DOM 对象*/// 如果 callback 参数是函数类型if (typeof callback === 'function') {// 使用 originalCallback 存储 callback 函数const originalCallback = callback;// 为 callback 参数重新赋值callback = function () {// 获取 render 方法第一个参数的真实 DOM 对象// 实际上就是 id="root" 的 div 的子元素// rootFiber.child.stateNode// rootFiber 就是 id="root" 的 divconst instance = getPublicRootInstance(fiberRoot);// 调用原始 callback 函数并改变函数内部 this 指向originalCallback.call(instance);};}// 初始化渲染不执行批量更新// 因为批量更新是异步的是可以被打断的, 但是初始化渲染应该尽快完成不能被打断// 所以不执行批量更新unbatchedUpdates(() => {updateContainer(children, fiberRoot, parentComponent, callback);});} else {// 非初始化渲染 即更新fiberRoot = root._internalRoot;if (typeof callback === 'function') {const originalCallback = callback;callback = function () {const instance = getPublicRootInstance(fiberRoot);originalCallback.call(instance);};}// UpdateupdateContainer(children, fiberRoot, parentComponent, callback);}// 返回 render 方法第一个参数的真实 DOM 对象作为 render 方法的返回值// 就是说渲染谁 返回谁的真实 DOM 对象return getPublicRootInstance(fiberRoot);
}export function findDOMNode(componentOrElement: Element | ?React$Component<any, any>,
): null | Element | Text {if (__DEV__) {let owner = (ReactCurrentOwner.current: any);if (owner !== null && owner.stateNode !== null) {const warnedAboutRefsInRender = owner.stateNode._warnedAboutRefsInRender;if (!warnedAboutRefsInRender) {console.error('%s is accessing findDOMNode inside its render(). ' +'render() should be a pure function of props and state. It should ' +'never access something that requires stale data from the previous ' +'render, such as refs. Move this logic to componentDidMount and ' +'componentDidUpdate instead.',getComponentName(owner.type) || 'A component',);}owner.stateNode._warnedAboutRefsInRender = true;}}if (componentOrElement == null) {return null;}if ((componentOrElement: any).nodeType === ELEMENT_NODE) {return (componentOrElement: any);}if (__DEV__) {return findHostInstanceWithWarning(componentOrElement, 'findDOMNode');}return findHostInstance(componentOrElement);
}export function hydrate(element: React$Node,container: Container,callback: ?Function,
) {invariant(isValidContainer(container),'Target container is not a DOM element.',);if (__DEV__) {const isModernRoot =isContainerMarkedAsRoot(container) &&container._reactRootContainer === undefined;if (isModernRoot) {console.error('You are calling ReactDOM.hydrate() on a container that was previously ' +'passed to ReactDOM.createRoot(). This is not supported. ' +'Did you mean to call createRoot(container, {hydrate: true}).render(element)?',);}}// TODO: throw or warn if we couldn't hydrate?return legacyRenderSubtreeIntoContainer(null,element,container,true,callback,);
}
/*** 渲染入口* element 要进行渲染的 ReactElement* container 渲染容器* callback 渲染完成后执行的回调函数*/
export function render(element: React$Element<any>,container: Container,callback: ?Function,
) {// 检测 container 是否是符合要求的渲染容器// 即检测 container 是否是真实的DOM对象// 如果不符合要求就报错invariant(isValidContainer(container),'Target container is not a DOM element.',);// 在开发环境下if (__DEV__) {// 检测 container 是否已经传递给 ReactDOM.createRoot() 方法// 如果已经传递, 则 container 不能再传递给 render 方法// 防止 render 方法和 createRoot 方法重复调用const isModernRoot =isContainerMarkedAsRoot(container) &&container._reactRootContainer === undefined;// 如果 container 已经传递给过 createRoot 方法if (isModernRoot) {// 在控制台报错console.error('You are calling ReactDOM.render() on a container that was previously ' +'passed to ReactDOM.createRoot(). This is not supported. ' +'Did you mean to call root.render(element)?',);}}return legacyRenderSubtreeIntoContainer(// 父组件 初始渲染没有父组件 传递 null 占位null,element,container,// 是否为服务器端渲染 false 不是服务器端渲染 true 是服务器端渲染false,callback,);
}export function unstable_renderSubtreeIntoContainer(parentComponent: React$Component<any, any>,element: React$Element<any>,containerNode: Container,callback: ?Function,
) {invariant(isValidContainer(containerNode),'Target container is not a DOM element.',);invariant(parentComponent != null && hasInstance(parentComponent),'parentComponent must be a valid React Component',);return legacyRenderSubtreeIntoContainer(parentComponent,element,containerNode,false,callback,);
}export function unmountComponentAtNode(container: Container) {invariant(isValidContainer(container),'unmountComponentAtNode(...): Target container is not a DOM element.',);if (__DEV__) {const isModernRoot =isContainerMarkedAsRoot(container) &&container._reactRootContainer === undefined;if (isModernRoot) {console.error('You are calling ReactDOM.unmountComponentAtNode() on a container that was previously ' +'passed to ReactDOM.createRoot(). This is not supported. Did you mean to call root.unmount()?',);}}if (container._reactRootContainer) {if (__DEV__) {const rootEl = getReactRootElementInContainer(container);const renderedByDifferentReact = rootEl && !getInstanceFromNode(rootEl);if (renderedByDifferentReact) {console.error("unmountComponentAtNode(): The node you're attempting to unmount " +'was rendered by another copy of React.',);}}// Unmount should not be batched.unbatchedUpdates(() => {legacyRenderSubtreeIntoContainer(null, null, container, false, () => {// $FlowFixMe This should probably use `delete container._reactRootContainer`container._reactRootContainer = null;unmarkContainerAsRoot(container);});});// If you call unmountComponentAtNode twice in quick succession, you'll// get `true` twice. That's probably fine?return true;} else {if (__DEV__) {const rootEl = getReactRootElementInContainer(container);const hasNonRootReactChild = !!(rootEl && getInstanceFromNode(rootEl));// Check if the container itself is a React root node.const isContainerReactRoot =container.nodeType === ELEMENT_NODE &&isValidContainer(container.parentNode) &&!!container.parentNode._reactRootContainer;if (hasNonRootReactChild) {console.error("unmountComponentAtNode(): The node you're attempting to unmount " +'was rendered by React and is not a top-level container. %s',isContainerReactRoot? 'You may have accidentally passed in a React root node instead ' +'of its container.': 'Instead, have the parent component update its state and ' +'rerender in order to remove this component.',);}}return false;}
}

render方法是react向外部对开发者开放的渲染入口方法。调用它时需要传入三个参数:

  1. element: 要进行渲染的ReactElement,也就是createElement方法的返回值。
  2. container:渲染容器,就是id为root的那个div的DOM对象。
  3. callback:渲染完成后要执行的回调函数,是一个可选参数。

例如:

ReactDOM.render(<App />/*element参数*/, document.getElementById('root')/*container参数*/, function () {console.log('callback')
}/*callback参数回调函数*/)

在render方法中,依次做以下操作:

  1. 首先调用 invariant 方法,检测container是否是符合要求的渲染容器。也就是要检测container是否是真实的DOM对象,如果不符合要求就会报错。其具体判逻辑如下代码示:

    // src/react/packages/react-dom/src/client/ReactDOMRoot.js
    /*** 判断 node 是否是符合要求的 DOM 节点* 1. node 可以是元素节点* 2. node 可以是 document 节点* 3. node 可以是 文档碎片节点* 4. node 可以是注释节点但注释内容必须是 react-mount-point-unstable*    react 内部会找到注释节点的父级 通过调用父级元素的 insertBefore 方法*    将 element 插入到注释节点的前面*/
    export function isValidContainer(node: mixed): boolean {return !!(node &&(node.nodeType === ELEMENT_NODE ||node.nodeType === DOCUMENT_NODE ||node.nodeType === DOCUMENT_FRAGMENT_NODE ||(node.nodeType === COMMENT_NODE &&(node: any).nodeValue === ' react-mount-point-unstable ')));
    }
  2. 进行开发环境判断,处理相关的开发使用错误提示。
  3. 返回 legacyRenderSubtreeIntoContainer()调用后的返回值。

    legacyRenderSubtreeIntoContainer方法是要将子树渲染到容器中 (初始化 Fiber 数据结构: 创建 fiberRoot 及 rootFiber)并返回。

    返回 render 方法第一个参数的真实 DOM 对象作为 render 方法的返回值,就是说渲染谁 返回谁的真实 DOM 对象。

    legacyRenderSubtreeIntoContainer方法的定义如下:

    /*** 将子树渲染到容器中 (初始化 Fiber 数据结构: 创建 fiberRoot 及 rootFiber)* parentComponent: 父组件, 初始渲染传入了 null* children: render 方法中的第一个参数, 要渲染的 ReactElement* container: 渲染容器* forceHydrate: true 为服务端渲染, false 为客户端渲染* callback: 组件渲染完成后需要执行的回调函数**/
    function legacyRenderSubtreeIntoContainer(parentComponent: ?React$Component<any, any>,children: ReactNodeList,container: Container,forceHydrate: boolean,callback: ?Function,
    ) {if (__DEV__) {topLevelUpdateWarnings(container);warnOnInvalidCallback(callback === undefined ? null : callback, 'render');}/*** 检测 container 是否已经是初始化过的渲染容器* react 在初始渲染时会为最外层容器添加 _reactRootContainer 属性* react 会根据此属性进行不同的渲染方式* root 不存在 表示初始渲染* root 存在 表示更新*/// 获取 container 容器对象下是否有 _reactRootContainer 属性let root: RootType = (container._reactRootContainer: any);// 即将存储根 Fiber 对象let fiberRoot;if (!root) {// 初始渲染// 初始化根 Fiber 数据结构// 为 container 容器添加 _reactRootContainer 属性// 在 _reactRootContainer 对象中有一个属性叫做 _internalRoot// _internalRoot 属性值即为 FiberRoot 表示根节点 Fiber 数据结构// legacyCreateRootFromDOMContainer// createLegacyRoot// new ReactDOMBlockingRoot -> this._internalRoot// createRootImplroot = container._reactRootContainer = legacyCreateRootFromDOMContainer(container,forceHydrate,);// 获取 Fiber Root 对象fiberRoot = root._internalRoot;/*** 改变 callback 函数中的 this 指向* 使其指向 render 方法第一个参数的真实 DOM 对象*/// 如果 callback 参数是函数类型if (typeof callback === 'function') {// 使用 originalCallback 存储 callback 函数const originalCallback = callback;// 为 callback 参数重新赋值callback = function () {// 获取 render 方法第一个参数的真实 DOM 对象// 实际上就是 id="root" 的 div 的子元素// rootFiber.child.stateNode// rootFiber 就是 id="root" 的 divconst instance = getPublicRootInstance(fiberRoot);// 调用原始 callback 函数并改变函数内部 this 指向originalCallback.call(instance);};}// 初始化渲染不执行批量更新// 因为批量更新是异步的是可以被打断的, 但是初始化渲染应该尽快完成不能被打断// 所以不执行批量更新unbatchedUpdates(() => {updateContainer(children, fiberRoot, parentComponent, callback);});} else {// 非初始化渲染 即更新fiberRoot = root._internalRoot;if (typeof callback === 'function') {const originalCallback = callback;callback = function () {const instance = getPublicRootInstance(fiberRoot);originalCallback.call(instance);};}// UpdateupdateContainer(children, fiberRoot, parentComponent, callback);}// 返回 render 方法第一个参数的真实 DOM 对象作为 render 方法的返回值// 就是说渲染谁 返回谁的真实 DOM 对象return getPublicRootInstance(fiberRoot);
    }
    /*** 判断是否为服务器端渲染 如果不是服务器端渲染* 清空 container 容器中的节点*/
    function legacyCreateRootFromDOMContainer(container: Container,forceHydrate: boolean,
    ): RootType {// container => <div id="root"></div>// 检测是否为服务器端渲染const shouldHydrate =forceHydrate || shouldHydrateDueToLegacyHeuristic(container);// 如果不是服务器端渲染if (!shouldHydrate) {let warned = false;let rootSibling;// 开启循环 删除 container 容器中的节点while ((rootSibling = container.lastChild)) {// 在开发环境中if (__DEV__) {/*** 判断子节点是否为元素节点, 并且元素节点不能有 data-reactroot 属性, 否则报错* 通常在服务器端渲染时会遇到这个问题** <div id="app">*  <%- markup %>* </div>* ↑ 报错** <div id="app"><%- markup %></div>* ↑ 不报错 删除了所有空白区域*/if (!warned &&rootSibling.nodeType === ELEMENT_NODE &&// 判断是否是服务端渲染的标志(rootSibling: any).hasAttribute(ROOT_ATTRIBUTE_NAME)) {warned = true;console.error('render(): Target node has markup rendered by React, but there ' +'are unrelated nodes as well. This is most commonly caused by ' +'white-space inserted around server-rendered markup.',);}}// 删除 container 容器中的节点container.removeChild(rootSibling);/*** 为什么要清除 container 中的元素 ?* 有时需要在 container 中放置一些占位图或者 loading 图以提高首屏加载用户体验,* 就无可避免的要向 container 中加入 html 标记.* 在将 ReactElement 渲染到 container 之前, 必然要先清空 container* 因为占位图和 ReactElement 不能同时显示** 在加入占位代码时, 最好只有一个父级元素, 可以减少内部代码的循环次数以提高性能* <div>*  <p>placement<p>*  <p>placement<p>*  <p>placement<p>* </div>*/}}// 在开发环境下if (__DEV__) {// 如果是服务器端渲染 控制台提示错误if (shouldHydrate && !forceHydrate && !warnedAboutHydrateAPI) {warnedAboutHydrateAPI = true;// 在客户端二次渲染时不要使用 render 方法, 要使用 hydrate 方法替代console.warn('render(): Calling ReactDOM.render() to hydrate server-rendered markup ' +'will stop working in React v17. Replace the ReactDOM.render() call ' +'with ReactDOM.hydrate() if you want React to attach to the server HTML.',);}}return createLegacyRoot(container,shouldHydrate? {hydrate: true,}: undefined,);
    }

    这里又调用了引入文件中的createLegacyRoot方法:

    // src/react/packages/react-dom/src/client/ReactDOMRoot.js
    /*** 通过实例化 ReactDOMBlockingRoot 类创建 LegacyRoot*/
    export function createLegacyRoot(container: Container,options?: RootOptions,
    ): RootType {// container => <div id="root"></div>// LegacyRoot 常量, 值为 0,// 通过 render 方法创建的 container 就是 LegacyRootreturn new ReactDOMBlockingRoot(container, LegacyRoot, options);
    }
    /*** 创建 ReactDOMBlockingRoot 的类* 通过它可以创建 LegacyRoot 的 Fiber 数据结构*/
    function ReactDOMBlockingRoot(container: Container,tag: RootTag,options: void | RootOptions,
    ) {// tag => 0 => legacyRoot// container => <div id="root"></div>// container._reactRootContainer = {_internalRoot: {}}this._internalRoot = createRootImpl(container, tag, options);
    }
    function createRootImpl(container: Container,tag: RootTag,options: void | RootOptions,
    ) {// container => <div id="root"></div>// tag => 0// options => undefined// 检测是否为服务器端渲染 falseconst hydrate = options != null && options.hydrate === true;// 服务器端渲染相关 nullconst hydrationCallbacks =(options != null && options.hydrationOptions) || null;const root = createContainer(container, tag, hydrate, hydrationCallbacks);markContainerAsRoot(root.current, container);// 服务器端渲染相关if (hydrate && tag !== LegacyRoot) {const doc =container.nodeType === DOCUMENT_NODE? container: container.ownerDocument;eagerlyTrapReplayableEvents(container, doc);}return root;
    }

    排除了与环境以及服务器渲染相关的代码,我们当前关注关键在于这两句代码:

    const root = createContainer(container, tag, hydrate, hydrationCallbacks);
    markContainerAsRoot(root.current, container);

    我们找到createContainer方法的定义

    // src/react/packages/react-reconciler/src/ReactFiberReconciler.js
    export function createContainer(containerInfo: Container,tag: RootTag,hydrate: boolean,hydrationCallbacks: null | SuspenseHydrationCallbacks,
    ): OpaqueRoot {// containerInfo => <div id="root"></div>// tag: 0// hydrate: false// hydrationCallbacks: nullreturn createFiberRoot(containerInfo, tag, hydrate, hydrationCallbacks);
    }

    这里又调用了createFiberRoot方法,它是在"src/react/packages/react-reconciler/src/ReactFiberRoot.js"文件中定义的,具体实现如下:

    // src/react/packages/react-reconciler/src/ReactFiberRoot.js// 创建根节点对应的 fiber 对象
    export function createFiberRoot(containerInfo: any,tag: RootTag,hydrate: boolean,hydrationCallbacks: null | SuspenseHydrationCallbacks,
    ): FiberRoot {// 创建 FiberRootconst root: FiberRoot = (new FiberRootNode(containerInfo, tag, hydrate): any);// falseif (enableSuspenseCallback) {root.hydrationCallbacks = hydrationCallbacks;}// 创建根节点对应的 rootFiberconst uninitializedFiber = createHostRootFiber(tag);// 为 fiberRoot 添加 current 属性 值为 rootFiberroot.current = uninitializedFiber;// 为 rootFiber 添加 stateNode 属性 值为 fiberRootuninitializedFiber.stateNode = root;// 为 fiber 对象添加 updateQueue 属性, 初始化 updateQueue 对象// updateQueue 用于存放 Update 对象// Update 对象用于记录组件状态的改变initializeUpdateQueue(uninitializedFiber);// 返回 rootreturn root;
    }

    在该方法中我们可以观察到其中有我们实现逻辑中比较重要的关键代码。
    new FiberRootNode()其实内部比较简单,就是利用参数构建Fiber对象实例并为其设置一些初始化默认属性数据,如下:

    function FiberRootNode(containerInfo, tag, hydrate) {this.tag = tag;this.current = null;this.containerInfo = containerInfo;this.pendingChildren = null;this.pingCache = null;this.finishedExpirationTime = NoWork;this.finishedWork = null;this.timeoutHandle = noTimeout;this.context = null;this.pendingContext = null;this.hydrate = hydrate;this.callbackNode = null;this.callbackPriority = NoPriority;this.firstPendingTime = NoWork;this.firstSuspendedTime = NoWork;this.lastSuspendedTime = NoWork;this.nextKnownPendingLevel = NoWork;this.lastPingedTime = NoWork;this.lastExpiredTime = NoWork;if (enableSchedulerTracing) {this.interactionThreadID = unstable_getThreadID();this.memoizedInteractions = new Set();this.pendingInteractionMap = new Map();}if (enableSuspenseCallback) {this.hydrationCallbacks = null;}
    }

    初始化记录上一次更新之后的state,作为下一次更新的基础。

    export function initializeUpdateQueue<State>(fiber: Fiber): void {const queue: UpdateQueue<State> = {baseState: fiber.memoizedState,baseQueue: null,shared: {pending: null,},effects: null,};fiber.updateQueue = queue;
    }
  4. 获取fiberRoot对象后,改变callback函数中的 this 指向,使其指向render方法第一个参数的真实 DOM 对象。

  5. 初始化渲染,不执行批量更新。因为批量更新是异步的是可以被打断的,但是初始化渲染应该尽快完成不能被打断,所以这一步不执行批量更新。

    unbatchedUpdates(() => {updateContainer(children, fiberRoot, parentComponent, callback);
    });
  6. updateContainer 方法是 中定义的。该方法最核心的事情就是去创建一个任务,然后把创建好的任务添加到任务队列中,等待浏览器执行。

    /*** 计算任务的过期时间* 再根据任务过期时间创建 Update 任务* 通过任务的过期时间还可以计算出任务的优先级*/
    export function updateContainer(// element 要渲染的 ReactElementelement: ReactNodeList,// container Fiber Root 对象container: OpaqueRoot,// parentComponent 父组件 初始渲染为 nullparentComponent: ?React$Component<any, any>,// ReactElement 渲染完成执行的回调函数callback: ?Function,
    ): ExpirationTime {if (__DEV__) {onScheduleRoot(container, element);}// container 获取 rootFiberconst current = container.current;// 获取当前距离 react 应用初始化的时间 1073741805const currentTime = requestCurrentTimeForUpdate();if (__DEV__) {// $FlowExpectedError - jest isn't a global, and isn't recognized outside of testsif ('undefined' !== typeof jest) {warnIfUnmockedScheduler(current);warnIfNotScopedWithMatchingAct(current);}}// 异步加载设置const suspenseConfig = requestCurrentSuspenseConfig();// 计算过期时间// 为防止任务因为优先级的原因一直被打断而未能执行// react 会设置一个过期时间, 当时间到了过期时间的时候// 如果任务还未执行的话, react 将会强制执行该任务// 初始化渲染时, 任务同步执行不涉及被打断的问题// 过期时间被设置成了 1073741823, 这个数值表示当前任务为同步任务const expirationTime = computeExpirationForFiber(currentTime,current,suspenseConfig,);// 设置FiberRoot.context, 首次执行返回一个emptyContext, 是一个 {}const context = getContextForSubtree(parentComponent);// 初始渲染时 Fiber Root 对象中的 context 属性值为 null// 所以会进入到 if 中if (container.context === null) {// 初始渲染时将 context 属性值设置为 {}container.context = context;} else {container.pendingContext = context;}if (__DEV__) {if (ReactCurrentFiberIsRendering &&ReactCurrentFiberCurrent !== null &&!didWarnAboutNestedUpdates) {didWarnAboutNestedUpdates = true;console.error('Render methods should be a pure function of props and state; ' +'triggering nested component updates from render is not allowed. ' +'If necessary, trigger nested updates in componentDidUpdate.\n\n' +'Check the render method of %s.',getComponentName(ReactCurrentFiberCurrent.type) || 'Unknown',);}}// 创建一个待执行任务const update = createUpdate(expirationTime, suspenseConfig);// 将要更新的内容挂载到更新对象中的 payload 中// 将要更新的组件存储在 payload 对象中, 方便后期获取update.payload = {element};// 判断 callback 是否存在callback = callback === undefined ? null : callback;// 如果 callback 存在if (callback !== null) {if (__DEV__) {if (typeof callback !== 'function') {console.error('render(...): Expected the last optional `callback` argument to be a ' +'function. Instead received: %s.',callback,);}}// 将 callback 挂载到 update 对象中// 其实就是一层层传递 方便 ReactElement 元素渲染完成调用// 回调函数执行完成后会被清除 可以在代码的后面加上 return 进行验证update.callback = callback;}// 将 update 对象加入到当前 Fiber 的更新队列当中 (updateQueue)// 待执行的任务都会被存储在 fiber.updateQueue.shared.pending 中enqueueUpdate(current, update);// 调度和更新 current 对象scheduleWork(current, expirationTime);// 返回过期时间return expirationTime;
    }
  7. 任务执行前的准备工作,开始执行任务,调用scheduleWork 方法调度和更新 current 对象
    scheduleWork实际上是指向另外一个方法的,它就是scheduleUpdateOnFiber 方法。该方法所做的核心事情就是判断任务是否为同步任务,是就调用同步任务入口performSyncSyncWorkOnRoot 方法去执行任务。

    /*** 判断任务是否为同步 调用同步任务入口*/
    export function scheduleUpdateOnFiber(fiber: Fiber,expirationTime: ExpirationTime,
    ) {/*** fiber: 初始化渲染时为 rootFiber, 即 <div id="root"></div> 对应的 Fiber 对象* expirationTime: 任务过期时间 => 1073741823*//*** 判断是否是无限循环的 update 如果是就报错* 在 componentWillUpdate 或者 componentDidUpdate 生命周期函数中重复调用* setState 方法时, 可能会发生这种情况, React 限制了嵌套更新的数量以防止无限循环* 限制的嵌套更新数量为 50, 可通过 NESTED_UPDATE_LIMIT 全局变量获取*/checkForNestedUpdates();// 开发环境下执行的代码 忽略warnAboutRenderPhaseUpdatesInDEV(fiber);// 遍历更新子节点的过期时间 返回 FiberRootconst root = markUpdateTimeFromFiberToRoot(fiber, expirationTime);if (root === null) {// 开发环境下执行 忽略warnAboutUpdateOnUnmountedFiberInDEV(fiber);return;}// 判断是否有高优先级任务打断当前正在执行的任务// 内部判断条件不成立 内部代码没有得到执行checkForInterruption(fiber, expirationTime);// 报告调度更新, 测试环境执行, 忽略recordScheduleUpdate();// 获取当前调度任务的优先级 数值类型 从90开始 数值越大 优先级越高// 97 普通优先级任务const priorityLevel = getCurrentPriorityLevel();// 判断任务是否是同步任务 Sync的值为: 1073741823if (expirationTime === Sync) {if (// 检查是否处于非批量更新模式(executionContext & LegacyUnbatchedContext) !== NoContext &&// 检查是否没有处于正在进行渲染的任务(executionContext & (RenderContext | CommitContext)) === NoContext) {// 在根上注册待处理的交互, 以避免丢失跟踪的交互数据// 初始渲染时内部条件判断不成立, 内部代码没有得到执行schedulePendingInteractions(root, expirationTime);// 同步任务入口点performSyncWorkOnRoot(root);} else {ensureRootIsScheduled(root);schedulePendingInteractions(root, expirationTime);if (executionContext === NoContext) {// Flush the synchronous work now, unless we're already working or inside// a batch. This is intentionally inside scheduleUpdateOnFiber instead of// scheduleCallbackForFiber to preserve the ability to schedule a callback// without immediately flushing it. We only do this for user-initiated// updates, to preserve historical behavior of legacy mode.flushSyncCallbackQueue();}}} else {ensureRootIsScheduled(root);schedulePendingInteractions(root, expirationTime);}// 初始渲染不执行if ((executionContext & DiscreteEventContext) !== NoContext &&// Only updates at user-blocking priority or greater are considered// discrete, even inside a discrete event.(priorityLevel === UserBlockingPriority ||priorityLevel === ImmediatePriority)) {// This is the result of a discrete event. Track the lowest priority// discrete update per root so we can flush them early, if needed.if (rootsWithPendingDiscreteUpdates === null) {rootsWithPendingDiscreteUpdates = new Map([[root, expirationTime]]);} else {const lastDiscreteTime = rootsWithPendingDiscreteUpdates.get(root);if (lastDiscreteTime === undefined || lastDiscreteTime > expirationTime) {rootsWithPendingDiscreteUpdates.set(root, expirationTime);}}}
    }
    export const scheduleWork = scheduleUpdateOnFiber;
  8. 同步任务入口执行逻辑,performSyncWorkOnRoot 方法。该方法被调用表明进入了 render 阶段,构建 workInProgress Fiber 树(内存中构建)。

    /*** 判断任务是否为同步 调用同步任务入口*/
    export function scheduleUpdateOnFiber(fiber: Fiber,expirationTime: ExpirationTime,
    ) {/*** fiber: 初始化渲染时为 rootFiber, 即 <div id="root"></div> 对应的 Fiber 对象* expirationTime: 任务过期时间 => 1073741823*//*** 判断是否是无限循环的 update 如果是就报错* 在 componentWillUpdate 或者 componentDidUpdate 生命周期函数中重复调用* setState 方法时, 可能会发生这种情况, React 限制了嵌套更新的数量以防止无限循环* 限制的嵌套更新数量为 50, 可通过 NESTED_UPDATE_LIMIT 全局变量获取*/checkForNestedUpdates();// 开发环境下执行的代码 忽略warnAboutRenderPhaseUpdatesInDEV(fiber);// 遍历更新子节点的过期时间 返回 FiberRootconst root = markUpdateTimeFromFiberToRoot(fiber, expirationTime);if (root === null) {// 开发环境下执行 忽略warnAboutUpdateOnUnmountedFiberInDEV(fiber);return;}// 判断是否有高优先级任务打断当前正在执行的任务// 内部判断条件不成立 内部代码没有得到执行checkForInterruption(fiber, expirationTime);// 报告调度更新, 测试环境执行, 忽略recordScheduleUpdate();// 获取当前调度任务的优先级 数值类型 从90开始 数值越大 优先级越高// 97 普通优先级任务const priorityLevel = getCurrentPriorityLevel();// 判断任务是否是同步任务 Sync的值为: 1073741823if (expirationTime === Sync) {if (// 检查是否处于非批量更新模式(executionContext & LegacyUnbatchedContext) !== NoContext &&// 检查是否没有处于正在进行渲染的任务(executionContext & (RenderContext | CommitContext)) === NoContext) {// 在根上注册待处理的交互, 以避免丢失跟踪的交互数据// 初始渲染时内部条件判断不成立, 内部代码没有得到执行schedulePendingInteractions(root, expirationTime);// 同步任务入口点performSyncWorkOnRoot(root);} else {ensureRootIsScheduled(root);schedulePendingInteractions(root, expirationTime);if (executionContext === NoContext) {// Flush the synchronous work now, unless we're already working or inside// a batch. This is intentionally inside scheduleUpdateOnFiber instead of// scheduleCallbackForFiber to preserve the ability to schedule a callback// without immediately flushing it. We only do this for user-initiated// updates, to preserve historical behavior of legacy mode.flushSyncCallbackQueue();}}} else {ensureRootIsScheduled(root);schedulePendingInteractions(root, expirationTime);}// 初始渲染不执行if ((executionContext & DiscreteEventContext) !== NoContext &&// Only updates at user-blocking priority or greater are considered// discrete, even inside a discrete event.(priorityLevel === UserBlockingPriority ||priorityLevel === ImmediatePriority)) {// This is the result of a discrete event. Track the lowest priority// discrete update per root so we can flush them early, if needed.if (rootsWithPendingDiscreteUpdates === null) {rootsWithPendingDiscreteUpdates = new Map([[root, expirationTime]]);} else {const lastDiscreteTime = rootsWithPendingDiscreteUpdates.get(root);if (lastDiscreteTime === undefined || lastDiscreteTime > expirationTime) {rootsWithPendingDiscreteUpdates.set(root, expirationTime);}}}
    }
    export const scheduleWork = scheduleUpdateOnFiber;
  9. 创建workInProgree Fiber

    // 构建 workInProgress Fiber 树中的 rootFiber
    // 构建完成后会替换 current fiber
    // 初始渲染 pendingProps 为 null
    export function createWorkInProgress(current: Fiber, pendingProps: any): Fiber {// current: current Fiber 中的 rootFiber// 获取 current Fiber 对应的 workInProgress Fiberlet workInProgress = current.alternate;// 如果 workInProgress 不存在if (workInProgress === null) {// 创建 fiber 对象workInProgress = createFiber(current.tag,pendingProps,current.key,current.mode,);// 属性复用workInProgress.elementType = current.elementType;workInProgress.type = current.type;workInProgress.stateNode = current.stateNode;if (__DEV__) {// DEV-only fieldsif (enableUserTimingAPI) {workInProgress._debugID = current._debugID;}workInProgress._debugSource = current._debugSource;workInProgress._debugOwner = current._debugOwner;workInProgress._debugHookTypes = current._debugHookTypes;}// 使用 alternate 存储 currentworkInProgress.alternate = current;// 使用 alternate 存储 workInProgresscurrent.alternate = workInProgress;} else {workInProgress.pendingProps = pendingProps;// We already have an alternate.// Reset the effect tag.workInProgress.effectTag = NoEffect;// The effect list is no longer valid.workInProgress.nextEffect = null;workInProgress.firstEffect = null;workInProgress.lastEffect = null;if (enableProfilerTimer) {// We intentionally reset, rather than copy, actualDuration & actualStartTime.// This prevents time from endlessly accumulating in new commits.// This has the downside of resetting values for different priority renders,// But works for yielding (the common case) and should support resuming.workInProgress.actualDuration = 0;workInProgress.actualStartTime = -1;}}workInProgress.childExpirationTime = current.childExpirationTime;workInProgress.expirationTime = current.expirationTime;workInProgress.child = current.child;workInProgress.memoizedProps = current.memoizedProps;workInProgress.memoizedState = current.memoizedState;workInProgress.updateQueue = current.updateQueue;// Clone the dependencies object. This is mutated during the render phase, so// it cannot be shared with the current fiber.const currentDependencies = current.dependencies;workInProgress.dependencies =currentDependencies === null? null: {expirationTime: currentDependencies.expirationTime,firstContext: currentDependencies.firstContext,responders: currentDependencies.responders,};// These will be overridden during the parent's reconciliationworkInProgress.sibling = current.sibling;workInProgress.index = current.index;workInProgress.ref = current.ref;if (enableProfilerTimer) {workInProgress.selfBaseDuration = current.selfBaseDuration;workInProgress.treeBaseDuration = current.treeBaseDuration;}if (__DEV__) {workInProgress._debugNeedsRemount = current._debugNeedsRemount;switch (workInProgress.tag) {case IndeterminateComponent:case FunctionComponent:case SimpleMemoComponent:workInProgress.type = resolveFunctionForHotReloading(current.type);break;case ClassComponent:workInProgress.type = resolveClassForHotReloading(current.type);break;case ForwardRef:workInProgress.type = resolveForwardRefForHotReloading(current.type);break;default:break;}}return workInProgress;
    }
  10. workLoopSync 方法。

    function workLoopSync() {// workInProgress 是一个 fiber 对象// 它的值不为 null 意味着该 fiber 对象上仍然有更新要执行// while 方法支撑 render 阶段 所有 fiber 节点的构建while (workInProgress !== null) {workInProgress = performUnitOfWork(workInProgress);}
    }
  11. performUnitOfWork 方法

    // 构建 Fiber 对象
    function performUnitOfWork(unitOfWork: Fiber): Fiber | null {// unitOfWork => workInProgress Fiber 树中的 rootFiber// current => currentFiber 树中的 rootFiberconst current = unitOfWork.alternate;startWorkTimer(unitOfWork);// 开发环境执行 忽略setCurrentDebugFiberInDEV(unitOfWork);// 存储下一个要构建的子级 Fiber 对象let next;// 初始渲染 不执行// falseif (enableProfilerTimer && (unitOfWork.mode & ProfileMode) !== NoMode) {startProfilerTimer(unitOfWork);next = beginWork(current, unitOfWork, renderExpirationTime);stopProfilerTimerIfRunningAndRecordDelta(unitOfWork, true);} else {// beginWork: 从父到子, 构建 Fiber 节点对象// 返回值 next 为当前节点的子节点next = beginWork(current, unitOfWork, renderExpirationTime);}// 开发环境执行 忽略resetCurrentDebugFiberInDEV();// 为旧的 props 属性赋值// 此次更新后 pendingProps 变为 memoizedPropsunitOfWork.memoizedProps = unitOfWork.pendingProps;// 如果子节点不存在说明当前节点向下遍历子节点已经到底了// 继续向上返回 遇到兄弟节点 构建兄弟节点的子 Fiber 对象 直到返回到根 Fiber 对象if (next === null) {// 从子到父, 构建其余节点 Fiber 对象next = completeUnitOfWork(unitOfWork);}ReactCurrentOwner.current = null;return next;
    }
  12. beginWork 方法做的事情是从父到子构建 Fiber 节点对象。

    // 从父到子, 构建 Fiber 节点对象
    function beginWork(current: Fiber | null,workInProgress: Fiber,renderExpirationTime: ExpirationTime,
    ): Fiber | null {// 1073741823const updateExpirationTime = workInProgress.expirationTime;if (__DEV__) {if (workInProgress._debugNeedsRemount && current !== null) {// This will restart the begin phase with a new fiber.return remountFiber(current,workInProgress,createFiberFromTypeAndProps(workInProgress.type,workInProgress.key,workInProgress.pendingProps,workInProgress._debugOwner || null,workInProgress.mode,workInProgress.expirationTime,),);}}// 判断是否有旧的 Fiber 对象// 初始渲染时 只有 rootFiber 节点存在 currentif (current !== null) {// 获取旧的 props 对象const oldProps = current.memoizedProps;// 获取新的 props 对象const newProps = workInProgress.pendingProps;// 初始渲染时 falseif (oldProps !== newProps ||hasLegacyContextChanged() ||// Force a re-render if the implementation changed due to hot reload:(__DEV__ ? workInProgress.type !== current.type : false)) {// If props or context changed, mark the fiber as having performed work.// This may be unset if the props are determined to be equal later (memo).didReceiveUpdate = true;// 初始渲染时 false 两个值相同} else if (updateExpirationTime < renderExpirationTime) {// 初始渲染时 false// 可以直接复用前一次更新的子Fiber, 不需要新建子FiberdidReceiveUpdate = false;// This fiber does not have any pending work. Bailout without entering// the begin phase. There's still some bookkeeping we that needs to be done// in this optimized path, mostly pushing stuff onto the stack.switch (workInProgress.tag) {case HostRoot:pushHostRootContext(workInProgress);resetHydrationState();break;case HostComponent:pushHostContext(workInProgress);if (workInProgress.mode & ConcurrentMode &&renderExpirationTime !== Never &&shouldDeprioritizeSubtree(workInProgress.type, newProps)) {if (enableSchedulerTracing) {markSpawnedWork(Never);}// Schedule this fiber to re-render at offscreen priority. Then bailout.workInProgress.expirationTime = workInProgress.childExpirationTime = Never;return null;}break;case ClassComponent: {const Component = workInProgress.type;if (isLegacyContextProvider(Component)) {pushLegacyContextProvider(workInProgress);}break;}case HostPortal:pushHostContainer(workInProgress,workInProgress.stateNode.containerInfo,);break;case ContextProvider: {const newValue = workInProgress.memoizedProps.value;pushProvider(workInProgress, newValue);break;}case Profiler:if (enableProfilerTimer) {// Profiler should only call onRender when one of its descendants actually rendered.const hasChildWork =workInProgress.childExpirationTime >= renderExpirationTime;if (hasChildWork) {workInProgress.effectTag |= Update;}}break;case SuspenseComponent: {const state: SuspenseState | null = workInProgress.memoizedState;if (state !== null) {if (enableSuspenseServerRenderer) {if (state.dehydrated !== null) {pushSuspenseContext(workInProgress,setDefaultShallowSuspenseContext(suspenseStackCursor.current),);// We know that this component will suspend again because if it has// been unsuspended it has committed as a resolved Suspense component.// If it needs to be retried, it should have work scheduled on it.workInProgress.effectTag |= DidCapture;break;}}// If this boundary is currently timed out, we need to decide// whether to retry the primary children, or to skip over it and// go straight to the fallback. Check the priority of the primary// child fragment.const primaryChildFragment: Fiber = (workInProgress.child: any);const primaryChildExpirationTime =primaryChildFragment.childExpirationTime;if (primaryChildExpirationTime !== NoWork &&primaryChildExpirationTime >= renderExpirationTime) {// The primary children have pending work. Use the normal path// to attempt to render the primary children again.return updateSuspenseComponent(current,workInProgress,renderExpirationTime,);} else {pushSuspenseContext(workInProgress,setDefaultShallowSuspenseContext(suspenseStackCursor.current),);// The primary children do not have pending work with sufficient// priority. Bailout.const child = bailoutOnAlreadyFinishedWork(current,workInProgress,renderExpirationTime,);if (child !== null) {// The fallback children have pending work. Skip over the// primary children and work on the fallback.return child.sibling;} else {return null;}}} else {pushSuspenseContext(workInProgress,setDefaultShallowSuspenseContext(suspenseStackCursor.current),);}break;}case SuspenseListComponent: {const didSuspendBefore =(current.effectTag & DidCapture) !== NoEffect;const hasChildWork =workInProgress.childExpirationTime >= renderExpirationTime;if (didSuspendBefore) {if (hasChildWork) {// If something was in fallback state last time, and we have all the// same children then we're still in progressive loading state.// Something might get unblocked by state updates or retries in the// tree which will affect the tail. So we need to use the normal// path to compute the correct tail.return updateSuspenseListComponent(current,workInProgress,renderExpirationTime,);}// If none of the children had any work, that means that none of// them got retried so they'll still be blocked in the same way// as before. We can fast bail out.workInProgress.effectTag |= DidCapture;}// If nothing suspended before and we're rendering the same children,// then the tail doesn't matter. Anything new that suspends will work// in the "together" mode, so we can continue from the state we had.let renderState = workInProgress.memoizedState;if (renderState !== null) {// Reset to the "together" mode in case we've started a different// update in the past but didn't complete it.renderState.rendering = null;renderState.tail = null;}pushSuspenseContext(workInProgress, suspenseStackCursor.current);if (hasChildWork) {break;} else {// If none of the children had any work, that means that none of// them got retried so they'll still be blocked in the same way// as before. We can fast bail out.return null;}}}// 复用 currentreturn bailoutOnAlreadyFinishedWork(current,workInProgress,renderExpirationTime,);} else {// An update was scheduled on this fiber, but there are no new props// nor legacy context. Set this to false. If an update queue or context// consumer produces a changed value, it will set this to true. Otherwise,// the component will assume the children have not changed and bail out.didReceiveUpdate = false;}} else {didReceiveUpdate = false;}// NoWork 常量 值为0 清空过期时间workInProgress.expirationTime = NoWork;// 根据当前 Fiber 的类型决定如何构建起子级 Fiber 对象// 文件位置: shared/ReactWorkTags.jsswitch (workInProgress.tag) {// 2// 函数组件在第一次被渲染时使用case IndeterminateComponent: {return mountIndeterminateComponent(// 旧 Fibercurrent,// 新 FiberworkInProgress,// 新 Fiber 的 type 值 初始渲染时是App组件函数workInProgress.type,// 同步 整数最大值 1073741823renderExpirationTime,);}// 16case LazyComponent: {const elementType = workInProgress.elementType;return mountLazyComponent(current,workInProgress,elementType,updateExpirationTime,renderExpirationTime,);}// 0case FunctionComponent: {const Component = workInProgress.type;const unresolvedProps = workInProgress.pendingProps;const resolvedProps =workInProgress.elementType === Component? unresolvedProps: resolveDefaultProps(Component, unresolvedProps);return updateFunctionComponent(current,workInProgress,Component,resolvedProps,renderExpirationTime,);}// 1case ClassComponent: {const Component = workInProgress.type;const unresolvedProps = workInProgress.pendingProps;const resolvedProps =workInProgress.elementType === Component? unresolvedProps: resolveDefaultProps(Component, unresolvedProps);return updateClassComponent(current,workInProgress,Component,resolvedProps,renderExpirationTime,);}// 3case HostRoot:return updateHostRoot(current, workInProgress, renderExpirationTime);// 5case HostComponent:return updateHostComponent(current, workInProgress, renderExpirationTime);// 6case HostText:return updateHostText(current, workInProgress);// 13case SuspenseComponent:return updateSuspenseComponent(current,workInProgress,renderExpirationTime,);// 4case HostPortal:return updatePortalComponent(current,workInProgress,renderExpirationTime,);// 11case ForwardRef: {const type = workInProgress.type;const unresolvedProps = workInProgress.pendingProps;const resolvedProps =workInProgress.elementType === type? unresolvedProps: resolveDefaultProps(type, unresolvedProps);return updateForwardRef(current,workInProgress,type,resolvedProps,renderExpirationTime,);}// 7case Fragment:return updateFragment(current, workInProgress, renderExpirationTime);// 8case Mode:return updateMode(current, workInProgress, renderExpirationTime);// 12case Profiler:return updateProfiler(current, workInProgress, renderExpirationTime);// 10case ContextProvider:return updateContextProvider(current,workInProgress,renderExpirationTime,);// 9case ContextConsumer:return updateContextConsumer(current,workInProgress,renderExpirationTime,);// 14case MemoComponent: {const type = workInProgress.type;const unresolvedProps = workInProgress.pendingProps;// Resolve outer props first, then resolve inner props.let resolvedProps = resolveDefaultProps(type, unresolvedProps);if (__DEV__) {if (workInProgress.type !== workInProgress.elementType) {const outerPropTypes = type.propTypes;if (outerPropTypes) {checkPropTypes(outerPropTypes,resolvedProps, // Resolved for outer only'prop',getComponentName(type),getCurrentFiberStackInDev,);}}}resolvedProps = resolveDefaultProps(type.type, resolvedProps);return updateMemoComponent(current,workInProgress,type,resolvedProps,updateExpirationTime,renderExpirationTime,);}// 15case SimpleMemoComponent: {return updateSimpleMemoComponent(current,workInProgress,workInProgress.type,workInProgress.pendingProps,updateExpirationTime,renderExpirationTime,);}// 17case IncompleteClassComponent: {const Component = workInProgress.type;const unresolvedProps = workInProgress.pendingProps;const resolvedProps =workInProgress.elementType === Component? unresolvedProps: resolveDefaultProps(Component, unresolvedProps);return mountIncompleteClassComponent(current,workInProgress,Component,resolvedProps,renderExpirationTime,);}// 19case SuspenseListComponent: {return updateSuspenseListComponent(current,workInProgress,renderExpirationTime,);}// 20case FundamentalComponent: {if (enableFundamentalAPI) {return updateFundamentalComponent(current,workInProgress,renderExpirationTime,);}break;}// 21case ScopeComponent: {if (enableScopeAPI) {return updateScopeComponent(current,workInProgress,renderExpirationTime,);}break;}// 22case Block: {if (enableBlocksAPI) {const block = workInProgress.type;const props = workInProgress.pendingProps;return updateBlock(current,workInProgress,block,props,renderExpirationTime,);}break;}}invariant(false,'Unknown unit of work tag (%s). This error is likely caused by a bug in ' +'React. Please file an issue.',workInProgress.tag,);
    }
  13. updateHostRoot 方法

    // 更新 hostRoot
    // <div id="root"></div> 对应的 Fiber 对象
    function updateHostRoot(current, workInProgress, renderExpirationTime) {pushHostRootContext(workInProgress);// 获取更新队列const updateQueue = workInProgress.updateQueue;invariant(current !== null && updateQueue !== null,'If the root does not have an updateQueue, we should have already ' +'bailed out. This error is likely caused by a bug in React. Please ' +'file an issue.',);// 获取新的 props 对象 nullconst nextProps = workInProgress.pendingProps;// 获取上一次渲染使用的 state nullconst prevState = workInProgress.memoizedState;// 获取上一次渲染使用的 children nullconst prevChildren = prevState !== null ? prevState.element : null;// 浅复制更新队列, 防止引用属性互相影响// workInProgress.updateQueue 浅拷贝 current.updateQueuecloneUpdateQueue(current, workInProgress);// 获取 updateQueue.payload 并赋值到 workInProgress.memoizedState// 要更新的内容就是 element 就是 rootFiber 的子元素processUpdateQueue(workInProgress, nextProps, null, renderExpirationTime);// 获取 element 所在对象const nextState = workInProgress.memoizedState;// 从对象中获取 elementconst nextChildren = nextState.element;// 在计算 state 后如果前后两个 Children 相同的情况// prevChildren => null// nextState => App// falseif (nextChildren === prevChildren) {// If the state is the same as before, that's a bailout because we had// no work that expires at this time.resetHydrationState();return bailoutOnAlreadyFinishedWork(current,workInProgress,renderExpirationTime,);}// 获取 fiberRoot 对象const root: FiberRoot = workInProgress.stateNode;// 服务器端渲染走 ifif (root.hydrate && enterHydrationState(workInProgress)) {// If we don't have any current children this might be the first pass.// We always try to hydrate. If this isn't a hydration pass there won't// be any children to hydrate which is effectively the same thing as// not hydrating.let child = mountChildFibers(workInProgress,null,nextChildren,renderExpirationTime,);workInProgress.child = child;let node = child;while (node) {// Mark each child as hydrating. This is a fast path to know whether this// tree is part of a hydrating tree. This is used to determine if a child// node has fully mounted yet, and for scheduling event replaying.// Conceptually this is similar to Placement in that a new subtree is// inserted into the React tree here. It just happens to not need DOM// mutations because it already exists.node.effectTag = (node.effectTag & ~Placement) | Hydrating;node = node.sibling;}} else {// 客户端渲染走 else// 构建子节点 fiber 对象reconcileChildren(current,workInProgress,nextChildren,renderExpirationTime,);resetHydrationState();}// 返回子节点 fiber 对象return workInProgress.child;
    }
    
  14. reconcileChildren 方法,构建子节点的 Fiber 对象。

    // 构建子级 Fiber 对象
    export function reconcileChildren(// 旧 Fibercurrent: Fiber | null,// 父级 FiberworkInProgress: Fiber,// 子级 vdom 对象nextChildren: any,// 初始渲染 整型最大值 代表同步任务renderExpirationTime: ExpirationTime,
    ) {/*** 为什么要传递 current ?* 如果不是初始渲染的情况, 要进行新旧 Fiber 对比* 初始渲染时则用不到 current*/// 如果就 Fiber 为 null 表示初始渲染if (current === null) {workInProgress.child = mountChildFibers(workInProgress,null,nextChildren,renderExpirationTime,);} else {// 否则就是更新// If the current child is the same as the work in progress, it means that// we haven't yet started any work on these children. Therefore, we use// the clone algorithm to create a copy of all the current children.// If we had any progressed work already, that is invalid at this point so// let's throw it out.workInProgress.child = reconcileChildFibers(workInProgress,current.child,nextChildren,renderExpirationTime,);}
    }
  15. mountChildFibers 方法和 reconcileChildFibers 方法。

    /*** shouldTrackSideEffects 标识, 是否为 Fiber 对象添加 effectTag* true 添加 false 不添加* 对于初始渲染来说, 只有根组件需要添加, 其他元素不需要添加, 防止过多的 DOM 操作*/
    // 用于更新
    export const reconcileChildFibers = ChildReconciler(true);
    // 用于初始渲染
    export const mountChildFibers = ChildReconciler(false);
    
  16. ChildReconciler(true|false) 方法设定当前的为初始化渲染还是更新操作,在进行后续操作。

    // This API will tag the children with the side-effect of the reconciliation// itself. They will be added to the side-effect list as we pass through the// children and the parent.function reconcileChildFibers(// 父 Fiber 对象returnFiber: Fiber,// 旧的第一个子 Fiber 初始渲染 nullcurrentFirstChild: Fiber | null,// 新的子 vdom 对象newChild: any,// 初始渲染 整型最大值 代表同步任务expirationTime: ExpirationTime,): Fiber | null {// 这是入口方法, 根据 newChild 类型进行对应处理// 判断新的子 vdom 是否为占位组件 比如 <></>// falseconst isUnkeyedTopLevelFragment =typeof newChild === 'object' &&newChild !== null &&newChild.type === REACT_FRAGMENT_TYPE &&newChild.key === null;// 如果 newChild 为占位符, 使用 占位符组件的子元素作为 newChildif (isUnkeyedTopLevelFragment) {newChild = newChild.props.children;}// 检测 newChild 是否为对象类型const isObject = typeof newChild === 'object' && newChild !== null;// newChild 是单个对象的情况if (isObject) {// 匹配子元素的类型switch (newChild.$$typeof) {// 子元素为 ReactElementcase REACT_ELEMENT_TYPE:// 为 Fiber 对象设置 effectTag 属性// 返回创建好的子 Fiberreturn placeSingleChild(// 处理单个 React Element 的情况// 内部会调用其他方法创建对应的 Fiber 对象reconcileSingleElement(returnFiber,currentFirstChild,newChild,expirationTime,),);case REACT_PORTAL_TYPE:return placeSingleChild(reconcileSinglePortal(returnFiber,currentFirstChild,newChild,expirationTime,),);}}// 处理 children 为文本和数值的情况 return "App works"if (typeof newChild === 'string' || typeof newChild === 'number') {return placeSingleChild(reconcileSingleTextNode(returnFiber,currentFirstChild,// 如果 newChild 是数值, 转换为字符串'' + newChild,expirationTime,),);}// children 是数组的情况if (isArray(newChild)) {// 返回创建好的子 Fiberreturn reconcileChildrenArray(returnFiber,currentFirstChild,newChild,expirationTime,);}if (getIteratorFn(newChild)) {return reconcileChildrenIterator(returnFiber,currentFirstChild,newChild,expirationTime,);}if (isObject) {throwOnInvalidObjectType(returnFiber, newChild);}if (__DEV__) {if (typeof newChild === 'function') {warnOnFunctionType();}}if (typeof newChild === 'undefined' && !isUnkeyedTopLevelFragment) {// If the new child is undefined, and the return fiber is a composite// component, throw an error. If Fiber return types are disabled,// we already threw above.switch (returnFiber.tag) {case ClassComponent: {if (__DEV__) {const instance = returnFiber.stateNode;if (instance.render._isMockFunction) {// We allow auto-mocks to proceed as if they're returning null.break;}}}// Intentionally fall through to the next case, which handles both// functions and classes// eslint-disable-next-lined no-fallthroughcase FunctionComponent: {const Component = returnFiber.type;invariant(false,'%s(...): Nothing was returned from render. This usually means a ' +'return statement is missing. Or, to render nothing, ' +'return null.',Component.displayName || Component.name || 'Component',);}}}// Remaining cases are all treated as empty.return deleteRemainingChildren(returnFiber, currentFirstChild);}return reconcileChildFibers;
  17. reconcileSingleElement 方法是处理子元素是单个对象的情况。

    // 处理子元素是单个对象的情况function reconcileSingleElement(// 父 Fiber 对象returnFiber: Fiber,// 备份子 fibercurrentFirstChild: Fiber | null,// 子 vdom 对象element: ReactElement,expirationTime: ExpirationTime,): Fiber {const key = element.key;let child = currentFirstChild;// 初始渲染 currentFirstChild 为 null// falsewhile (child !== null) {// TODO: If key === null and child.key === null, then this only applies to// the first item in the list.if (child.key === key) {switch (child.tag) {case Fragment: {if (element.type === REACT_FRAGMENT_TYPE) {deleteRemainingChildren(returnFiber, child.sibling);const existing = useFiber(child, element.props.children);existing.return = returnFiber;if (__DEV__) {existing._debugSource = element._source;existing._debugOwner = element._owner;}return existing;}break;}case Block:if (enableBlocksAPI) {if (element.type.$$typeof === REACT_BLOCK_TYPE &&element.type.render === child.type.render) {deleteRemainingChildren(returnFiber, child.sibling);const existing = useFiber(child, element.props);existing.type = element.type;existing.return = returnFiber;if (__DEV__) {existing._debugSource = element._source;existing._debugOwner = element._owner;}return existing;}}// We intentionally fallthrough here if enableBlocksAPI is not on.// eslint-disable-next-lined no-fallthroughdefault: {if (child.elementType === element.type ||// Keep this check inline so it only runs on the false path:(__DEV__? isCompatibleFamilyForHotReloading(child, element): false)) {deleteRemainingChildren(returnFiber, child.sibling);const existing = useFiber(child, element.props);existing.ref = coerceRef(returnFiber, child, element);existing.return = returnFiber;if (__DEV__) {existing._debugSource = element._source;existing._debugOwner = element._owner;}return existing;}break;}}// Didn't match.deleteRemainingChildren(returnFiber, child);break;} else {deleteChild(returnFiber, child);}child = child.sibling;}// 查看子 vdom 对象是否表示 fragment// falseif (element.type === REACT_FRAGMENT_TYPE) {const created = createFiberFromFragment(element.props.children,returnFiber.mode,expirationTime,element.key,);created.return = returnFiber;return created;} else {// 根据 React Element 创建 Fiber 对象// 返回创建好的 Fiber 对象const created = createFiberFromElement(element,// 用来表示当前组件下的所有子组件要用处于何种渲染模式// 文件位置: ./ReactTypeOfMode.js// 0    同步渲染模式// 100  异步渲染模式returnFiber.mode,expirationTime,);// 添加 ref 属性 { current: DOM }created.ref = coerceRef(returnFiber, currentFirstChild, element);// 添加父级 Fiber 对象created.return = returnFiber;// 返回创建好的子 Fiberreturn created;}}
  18. reconcileChildrenArray 方法 构建多个子级Fiber对象。

    // 处理子元素是数组的情况function reconcileChildrenArray(// 父级 FiberreturnFiber: Fiber,currentFirstChild: Fiber | null,// 子级 vdom 数组newChildren: Array<*>,expirationTime: ExpirationTime,): Fiber | null {if (__DEV__) {// First, validate keys.let knownKeys = null;for (let i = 0; i < newChildren.length; i++) {const child = newChildren[i];knownKeys = warnOnInvalidKey(child, knownKeys);}}/*** 存储第一个子节点 Fiber 对象* 方法返回的也是第一个子节点 Fiber 对象* 因为其他子节点 Fiber 对象都存储在上一个子 Fiber 节点对象的 sibling 属性中*/let resultingFirstChild: Fiber | null = null;// 上一次创建的 Fiber 对象let previousNewFiber: Fiber | null = null;// 初始渲染没有旧的子级 所以为 nulllet oldFiber = currentFirstChild;let lastPlacedIndex = 0;let newIdx = 0;let nextOldFiber = null;// 初始渲染 oldFiber 为 null 循环不执行for (; oldFiber !== null && newIdx < newChildren.length; newIdx++) {if (oldFiber.index > newIdx) {nextOldFiber = oldFiber;oldFiber = null;} else {nextOldFiber = oldFiber.sibling;}const newFiber = updateSlot(returnFiber,oldFiber,newChildren[newIdx],expirationTime,);if (newFiber === null) {// TODO: This breaks on empty slots like null children. That's// unfortunate because it triggers the slow path all the time. We need// a better way to communicate whether this was a miss or null,// boolean, undefined, etc.if (oldFiber === null) {oldFiber = nextOldFiber;}break;}if (shouldTrackSideEffects) {if (oldFiber && newFiber.alternate === null) {// We matched the slot, but we didn't reuse the existing fiber, so we// need to delete the existing child.deleteChild(returnFiber, oldFiber);}}lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);if (previousNewFiber === null) {// TODO: Move out of the loop. This only happens for the first run.resultingFirstChild = newFiber;} else {// TODO: Defer siblings if we're not at the right index for this slot.// I.e. if we had null values before, then we want to defer this// for each null value. However, we also don't want to call updateSlot// with the previous one.previousNewFiber.sibling = newFiber;}previousNewFiber = newFiber;oldFiber = nextOldFiber;}// 初始渲染不执行if (newIdx === newChildren.length) {// We've reached the end of the new children. We can delete the rest.deleteRemainingChildren(returnFiber, oldFiber);return resultingFirstChild;}// oldFiber 为空 说明是初始渲染if (oldFiber === null) {// 遍历子 vdom 对象for (; newIdx < newChildren.length; newIdx++) {// 创建子 vdom 对应的 fiber 对象const newFiber = createChild(returnFiber,newChildren[newIdx],expirationTime,);// 如果 newFiber 为 nullif (newFiber === null) {// 进入下次循环continue;}// 初始渲染时只为 newFiber 添加了 index 属性,// 其他事没干. lastPlacedIndex 被原封不动的返回了lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);// 为当前节点设置下一个兄弟节点if (previousNewFiber === null) {// 存储第一个子 Fiber 发生在第一次循环时resultingFirstChild = newFiber;} else {// 为节点设置下一个兄弟 FiberpreviousNewFiber.sibling = newFiber;}// 在循环的过程中更新上一个创建的Fiber 对象previousNewFiber = newFiber;}// 返回创建好的子 Fiber// 其他 Fiber 都作为 sibling 存在return resultingFirstChild;}// 下面的代码初始渲染不执行// Add all children to a key map for quick lookups.const existingChildren = mapRemainingChildren(returnFiber, oldFiber);// Keep scanning and use the map to restore deleted items as moves.for (; newIdx < newChildren.length; newIdx++) {const newFiber = updateFromMap(existingChildren,returnFiber,newIdx,newChildren[newIdx],expirationTime,);if (newFiber !== null) {if (shouldTrackSideEffects) {if (newFiber.alternate !== null) {// The new fiber is a work in progress, but if there exists a// current, that means that we reused the fiber. We need to delete// it from the child list so that we don't add it to the deletion// list.existingChildren.delete(newFiber.key === null ? newIdx : newFiber.key,);}}lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);if (previousNewFiber === null) {resultingFirstChild = newFiber;} else {previousNewFiber.sibling = newFiber;}previousNewFiber = newFiber;}}// 初始渲染不执行if (shouldTrackSideEffects) {// Any existing children that weren't consumed above were deleted. We need// to add them to the deletion list.existingChildren.forEach((child) => deleteChild(returnFiber, child));}// 返回第一个子元素 Fiber 对象return resultingFirstChild;}
  19. 子级节点Fiber对象的构建流程

    /*** 1. 创建 Fiber 对象* 2. 创建每一个节点的真实 DOM 对象并将其添加到 stateNode 属性中* 3. 收集要执行 DOM 操作的 Fiber 节点, 组建 effect 链表结构*/
    function completeUnitOfWork(unitOfWork: Fiber): Fiber | null {// 为 workInProgress 全局变量重新赋值workInProgress = unitOfWork;do {// 获取备份节点// 初始化渲染 非根 Fiber 对象没有备份节点 所以 current 为 nullconst current = workInProgress.alternate;// 父级 Fiber 对象, 非根 Fiber 对象都有父级const returnFiber = workInProgress.return;// 判断传入的 Fiber 对象是否构建完成, 任务调度相关// & 是表示位的与运算, 把左右两边的数字转化为二进制// 然后每一位分别进行比较, 如果相等就为1, 不相等即为0// 此处应用"位与"运算符的目的是"清零"// trueif ((workInProgress.effectTag & Incomplete) === NoEffect) {// 开发环境代码 忽略setCurrentDebugFiberInDEV(workInProgress);let next;// 如果不能使用分析器的 timer, 直接执行 completeWork// enableProfilerTimer => true// 但此处无论条件是否成立都会执行 completeWorkif (!enableProfilerTimer ||(workInProgress.mode & ProfileMode) === NoMode) {// 重点代码(二)// 创建节点真实 DOM 对象并将其添加到 stateNode 属性中next = completeWork(current, workInProgress, renderExpirationTime);} else {// 否则执行分析器timer, 并执行 completeWorkstartProfilerTimer(workInProgress);// 创建节点真实 DOM 对象并将其添加到 stateNode 属性中next = completeWork(current, workInProgress, renderExpirationTime);// Update render duration assuming we didn't error.stopProfilerTimerIfRunningAndRecordDelta(workInProgress, false);}stopWorkTimer(workInProgress);resetCurrentDebugFiberInDEV();resetChildExpirationTime(workInProgress);// 重点代码(一)// 如果子级存在if (next !== null) {// 返回子级 一直返回到 workLoopSync// 再重新执行 performUnitOfWork 构建子级 Fiber 节点对象return next;}// 构建 effect 链表结构// 如果不是根 Fiber 就是 true 否则就是 false// 将子树和此 Fiber 的所有 effect 附加到父级的 effect 列表中if (// 如果父 Fiber 存在 并且returnFiber !== null &&// 父 Fiber 对象中的 effectTag 为 0(returnFiber.effectTag & Incomplete) === NoEffect) {// 将子树和此 Fiber 的所有副作用附加到父级的 effect 列表上// 以下两个判断的作用是搜集子 Fiber的 effect 到父 Fiberif (returnFiber.firstEffect === null) {// firstreturnFiber.firstEffect = workInProgress.firstEffect;}if (workInProgress.lastEffect !== null) {if (returnFiber.lastEffect !== null) {// nextreturnFiber.lastEffect.nextEffect = workInProgress.firstEffect;}// lastreturnFiber.lastEffect = workInProgress.lastEffect;}// 获取副作用标记// 初始渲染时除[根组件]以外的 Fiber, effectTag 值都为 0, 即不需要执行任何真实DOM操作// 根组件的 effectTag 值为 3, 即需要将此节点对应的真实DOM对象添加到页面中const effectTag = workInProgress.effectTag;// 创建 effect 列表时跳过 NoWork(0) 和 PerformedWork(1) 标记// PerformedWork 由 React DevTools 读取, 不提交// 初始渲染时 只有遍历到了根组件 判断条件才能成立, 将 effect 链表添加到 rootFiber// 初始渲染 FiberRoot 对象中的 firstEffect 和 lastEffect 都是 App 组件// 因为当所有节点在内存中构建完成后, 只需要一次将所有 DOM 添加到页面中if (effectTag > PerformedWork) {// falseif (returnFiber.lastEffect !== null) {returnFiber.lastEffect.nextEffect = workInProgress;} else {// 为 fiberRoot 添加 firstEffectreturnFiber.firstEffect = workInProgress;}// 为 fiberRoot 添加 lastEffectreturnFiber.lastEffect = workInProgress;}}} else {// 初始渲染不执行// This fiber did not complete because something threw. Pop values off// the stack without entering the complete phase. If this is a boundary,// capture values if possible.const next = unwindWork(workInProgress, renderExpirationTime);// Because this fiber did not complete, don't reset its expiration time.if (enableProfilerTimer &&(workInProgress.mode & ProfileMode) !== NoMode) {// Record the render duration for the fiber that errored.stopProfilerTimerIfRunningAndRecordDelta(workInProgress, false);// Include the time spent working on failed children before continuing.let actualDuration = workInProgress.actualDuration;let child = workInProgress.child;while (child !== null) {actualDuration += child.actualDuration;child = child.sibling;}workInProgress.actualDuration = actualDuration;}if (next !== null) {// If completing this work spawned new work, do that next. We'll come// back here again.// Since we're restarting, remove anything that is not a host effect// from the effect tag.// TODO: The name stopFailedWorkTimer is misleading because Suspense// also captures and restarts.stopFailedWorkTimer(workInProgress);next.effectTag &= HostEffectMask;return next;}stopWorkTimer(workInProgress);if (returnFiber !== null) {// Mark the parent fiber as incomplete and clear its effect list.returnFiber.firstEffect = returnFiber.lastEffect = null;returnFiber.effectTag |= Incomplete;}}// 获取下一个同级 Fiber 对象const siblingFiber = workInProgress.sibling;// 如果下一个同级 Fiber 对象存在if (siblingFiber !== null) {// 返回下一个同级 Fiber 对象return siblingFiber;}// 否则退回父级workInProgress = returnFiber;} while (workInProgress !== null);// 当执行到这里的时候, 说明遍历到了 root 节点, 已完成遍历// 更新 workInProgressRootExitStatus 的状态为 已完成if (workInProgressRootExitStatus === RootIncomplete) {workInProgressRootExitStatus = RootCompleted;}return null;
    }

    completeWork 方法:

    function completeWork(current: Fiber | null,workInProgress: Fiber,renderExpirationTime: ExpirationTime,
    ): Fiber | null {// 获取待更新 propsconst newProps = workInProgress.pendingProps;switch (workInProgress.tag) {case IndeterminateComponent:case LazyComponent:case SimpleMemoComponent:// 0case FunctionComponent:case ForwardRef:case Fragment:case Mode:case Profiler:case ContextConsumer:case MemoComponent:return null;case ClassComponent: {const Component = workInProgress.type;if (isLegacyContextProvider(Component)) {popLegacyContext(workInProgress);}return null;}// 3case HostRoot: {popHostContainer(workInProgress);popTopLevelLegacyContextObject(workInProgress);const fiberRoot = (workInProgress.stateNode: FiberRoot);if (fiberRoot.pendingContext) {fiberRoot.context = fiberRoot.pendingContext;fiberRoot.pendingContext = null;}if (current === null || current.child === null) {// If we hydrated, pop so that we can delete any remaining children// that weren't hydrated.let wasHydrated = popHydrationState(workInProgress);if (wasHydrated) {// If we hydrated, then we'll need to schedule an update for// the commit side-effects on the root.markUpdate(workInProgress);}}updateHostContainer(workInProgress);return null;}// 5case HostComponent: {popHostContext(workInProgress);// 获取 rootDOM 节点 <div id="root"></div>const rootContainerInstance = getRootHostContainer();// 节点的具体的类型 div span ...const type = workInProgress.type;// 初始渲染不执行 current = nullif (current !== null && workInProgress.stateNode != null) {updateHostComponent(current,workInProgress,type,newProps,rootContainerInstance,);if (enableDeprecatedFlareAPI) {const prevListeners = current.memoizedProps.DEPRECATED_flareListeners;const nextListeners = newProps.DEPRECATED_flareListeners;if (prevListeners !== nextListeners) {markUpdate(workInProgress);}}if (current.ref !== workInProgress.ref) {markRef(workInProgress);}} else {if (!newProps) {invariant(workInProgress.stateNode !== null,'We must have new props for new mounts. This error is likely ' +'caused by a bug in React. Please file an issue.',);// This can happen when we abort work.return null;}const currentHostContext = getHostContext();// TODO: Move createInstance to beginWork and keep it on a context// "stack" as the parent. Then append children as we go in beginWork// or completeWork depending on whether we want to add them top->down or// bottom->up. Top->down is faster in IE11.let wasHydrated = popHydrationState(workInProgress);// 服务器渲染相关 初始渲染为不执行// falseif (wasHydrated) {// TODO: Move this and createInstance step into the beginPhase// to consolidate.if (prepareToHydrateHostInstance(workInProgress,rootContainerInstance,currentHostContext,)) {// If changes to the hydrated node need to be applied at the// commit-phase we mark this as such.markUpdate(workInProgress);}if (enableDeprecatedFlareAPI) {const listeners = newProps.DEPRECATED_flareListeners;if (listeners != null) {updateDeprecatedEventListeners(listeners,workInProgress,rootContainerInstance,);}}} else {// 创建节点实例对象 <div></div> <span></span>let instance = createInstance(type,newProps,rootContainerInstance,currentHostContext,workInProgress,);/*** 将所有的子级追加到父级中* instance 为父级* workInProgress.child 为子级*/appendAllChildren(instance, workInProgress, false, false);// 为 Fiber 对象添加 stateNode 属性workInProgress.stateNode = instance;// 初始渲染不执行// falseif (enableDeprecatedFlareAPI) {const listeners = newProps.DEPRECATED_flareListeners;if (listeners != null) {updateDeprecatedEventListeners(listeners,workInProgress,rootContainerInstance,);}}// Certain renderers require commit-time effects for initial mount.// (eg DOM renderer supports auto-focus for certain elements).// Make sure such renderers get scheduled for later work.// 初始渲染不执行if (finalizeInitialChildren(instance,type,newProps,rootContainerInstance,currentHostContext,)) {markUpdate(workInProgress);}}// 处理 ref DOM 引用if (workInProgress.ref !== null) {// If there is a ref on a host node we need to schedule a callbackmarkRef(workInProgress);}}return null;}// 6case HostText: {let newText = newProps;if (current && workInProgress.stateNode != null) {const oldText = current.memoizedProps;// If we have an alternate, that means this is an update and we need// to schedule a side-effect to do the updates.updateHostText(current, workInProgress, oldText, newText);} else {if (typeof newText !== 'string') {invariant(workInProgress.stateNode !== null,'We must have new props for new mounts. This error is likely ' +'caused by a bug in React. Please file an issue.',);// This can happen when we abort work.}const rootContainerInstance = getRootHostContainer();const currentHostContext = getHostContext();let wasHydrated = popHydrationState(workInProgress);if (wasHydrated) {if (prepareToHydrateHostTextInstance(workInProgress)) {markUpdate(workInProgress);}} else {workInProgress.stateNode = createTextInstance(newText,rootContainerInstance,currentHostContext,workInProgress,);}}return null;}case SuspenseComponent: {popSuspenseContext(workInProgress);const nextState: null | SuspenseState = workInProgress.memoizedState;if (enableSuspenseServerRenderer) {if (nextState !== null && nextState.dehydrated !== null) {if (current === null) {let wasHydrated = popHydrationState(workInProgress);invariant(wasHydrated,'A dehydrated suspense component was completed without a hydrated node. ' +'This is probably a bug in React.',);prepareToHydrateHostSuspenseInstance(workInProgress);if (enableSchedulerTracing) {markSpawnedWork(Never);}return null;} else {// We should never have been in a hydration state if we didn't have a current.// However, in some of those paths, we might have reentered a hydration state// and then we might be inside a hydration state. In that case, we'll need to exit out of it.resetHydrationState();if ((workInProgress.effectTag & DidCapture) === NoEffect) {// This boundary did not suspend so it's now hydrated and unsuspended.workInProgress.memoizedState = null;}// If nothing suspended, we need to schedule an effect to mark this boundary// as having hydrated so events know that they're free to be invoked.// It's also a signal to replay events and the suspense callback.// If something suspended, schedule an effect to attach retry listeners.// So we might as well always mark this.workInProgress.effectTag |= Update;return null;}}}if ((workInProgress.effectTag & DidCapture) !== NoEffect) {// Something suspended. Re-render with the fallback children.workInProgress.expirationTime = renderExpirationTime;// Do not reset the effect list.return workInProgress;}const nextDidTimeout = nextState !== null;let prevDidTimeout = false;if (current === null) {if (workInProgress.memoizedProps.fallback !== undefined) {popHydrationState(workInProgress);}} else {const prevState: null | SuspenseState = current.memoizedState;prevDidTimeout = prevState !== null;if (!nextDidTimeout && prevState !== null) {// We just switched from the fallback to the normal children.// Delete the fallback.// TODO: Would it be better to store the fallback fragment on// the stateNode during the begin phase?const currentFallbackChild: Fiber | null = (current.child: any).sibling;if (currentFallbackChild !== null) {// Deletions go at the beginning of the return fiber's effect listconst first = workInProgress.firstEffect;if (first !== null) {workInProgress.firstEffect = currentFallbackChild;currentFallbackChild.nextEffect = first;} else {workInProgress.firstEffect = workInProgress.lastEffect = currentFallbackChild;currentFallbackChild.nextEffect = null;}currentFallbackChild.effectTag = Deletion;}}}if (nextDidTimeout && !prevDidTimeout) {// If this subtreee is running in blocking mode we can suspend,// otherwise we won't suspend.// TODO: This will still suspend a synchronous tree if anything// in the concurrent tree already suspended during this render.// This is a known bug.if ((workInProgress.mode & BlockingMode) !== NoMode) {// TODO: Move this back to throwException because this is too late// if this is a large tree which is common for initial loads. We// don't know if we should restart a render or not until we get// this marker, and this is too late.// If this render already had a ping or lower pri updates,// and this is the first time we know we're going to suspend we// should be able to immediately restart from within throwException.const hasInvisibleChildContext =current === null &&workInProgress.memoizedProps.unstable_avoidThisFallback !== true;if (hasInvisibleChildContext ||hasSuspenseContext(suspenseStackCursor.current,(InvisibleParentSuspenseContext: SuspenseContext),)) {// If this was in an invisible tree or a new render, then showing// this boundary is ok.renderDidSuspend();} else {// Otherwise, we're going to have to hide content so we should// suspend for longer if possible.renderDidSuspendDelayIfPossible();}}}if (supportsPersistence) {// TODO: Only schedule updates if not prevDidTimeout.if (nextDidTimeout) {// If this boundary just timed out, schedule an effect to attach a// retry listener to the promise. This flag is also used to hide the// primary children.workInProgress.effectTag |= Update;}}if (supportsMutation) {// TODO: Only schedule updates if these values are non equal, i.e. it changed.if (nextDidTimeout || prevDidTimeout) {// If this boundary just timed out, schedule an effect to attach a// retry listener to the promise. This flag is also used to hide the// primary children. In mutation mode, we also need the flag to// *unhide* children that were previously hidden, so check if this// is currently timed out, too.workInProgress.effectTag |= Update;}}if (enableSuspenseCallback &&workInProgress.updateQueue !== null &&workInProgress.memoizedProps.suspenseCallback != null) {// Always notify the callbackworkInProgress.effectTag |= Update;}return null;}case HostPortal:popHostContainer(workInProgress);updateHostContainer(workInProgress);return null;case ContextProvider:// Pop provider fiberpopProvider(workInProgress);return null;case IncompleteClassComponent: {// Same as class component case. I put it down here so that the tags are// sequential to ensure this switch is compiled to a jump table.const Component = workInProgress.type;if (isLegacyContextProvider(Component)) {popLegacyContext(workInProgress);}return null;}case SuspenseListComponent: {popSuspenseContext(workInProgress);const renderState: null | SuspenseListRenderState =workInProgress.memoizedState;if (renderState === null) {// We're running in the default, "independent" mode.// We don't do anything in this mode.return null;}let didSuspendAlready =(workInProgress.effectTag & DidCapture) !== NoEffect;let renderedTail = renderState.rendering;if (renderedTail === null) {// We just rendered the head.if (!didSuspendAlready) {// This is the first pass. We need to figure out if anything is still// suspended in the rendered set.// If new content unsuspended, but there's still some content that// didn't. Then we need to do a second pass that forces everything// to keep showing their fallbacks.// We might be suspended if something in this render pass suspended, or// something in the previous committed pass suspended. Otherwise,// there's no chance so we can skip the expensive call to// findFirstSuspended.let cannotBeSuspended =renderHasNotSuspendedYet() &&(current === null || (current.effectTag & DidCapture) === NoEffect);if (!cannotBeSuspended) {let row = workInProgress.child;while (row !== null) {let suspended = findFirstSuspended(row);if (suspended !== null) {didSuspendAlready = true;workInProgress.effectTag |= DidCapture;cutOffTailIfNeeded(renderState, false);// If this is a newly suspended tree, it might not get committed as// part of the second pass. In that case nothing will subscribe to// its thennables. Instead, we'll transfer its thennables to the// SuspenseList so that it can retry if they resolve.// There might be multiple of these in the list but since we're// going to wait for all of them anyway, it doesn't really matter// which ones gets to ping. In theory we could get clever and keep// track of how many dependencies remain but it gets tricky because// in the meantime, we can add/remove/change items and dependencies.// We might bail out of the loop before finding any but that// doesn't matter since that means that the other boundaries that// we did find already has their listeners attached.let newThennables = suspended.updateQueue;if (newThennables !== null) {workInProgress.updateQueue = newThennables;workInProgress.effectTag |= Update;}// Rerender the whole list, but this time, we'll force fallbacks// to stay in place.// Reset the effect list before doing the second pass since that's now invalid.if (renderState.lastEffect === null) {workInProgress.firstEffect = null;}workInProgress.lastEffect = renderState.lastEffect;// Reset the child fibers to their original state.resetChildFibers(workInProgress, renderExpirationTime);// Set up the Suspense Context to force suspense and immediately// rerender the children.pushSuspenseContext(workInProgress,setShallowSuspenseContext(suspenseStackCursor.current,ForceSuspenseFallback,),);return workInProgress.child;}row = row.sibling;}}} else {cutOffTailIfNeeded(renderState, false);}// Next we're going to render the tail.} else {// Append the rendered row to the child list.if (!didSuspendAlready) {let suspended = findFirstSuspended(renderedTail);if (suspended !== null) {workInProgress.effectTag |= DidCapture;didSuspendAlready = true;// Ensure we transfer the update queue to the parent so that it doesn't// get lost if this row ends up dropped during a second pass.let newThennables = suspended.updateQueue;if (newThennables !== null) {workInProgress.updateQueue = newThennables;workInProgress.effectTag |= Update;}cutOffTailIfNeeded(renderState, true);// This might have been modified.if (renderState.tail === null &&renderState.tailMode === 'hidden' &&!renderedTail.alternate) {// We need to delete the row we just rendered.// Reset the effect list to what it was before we rendered this// child. The nested children have already appended themselves.let lastEffect = (workInProgress.lastEffect =renderState.lastEffect);// Remove any effects that were appended after this point.if (lastEffect !== null) {lastEffect.nextEffect = null;}// We're done.return null;}} else if (// The time it took to render last row is greater than time until// the expiration.now() * 2 - renderState.renderingStartTime >renderState.tailExpiration &&renderExpirationTime > Never) {// We have now passed our CPU deadline and we'll just give up further// attempts to render the main content and only render fallbacks.// The assumption is that this is usually faster.workInProgress.effectTag |= DidCapture;didSuspendAlready = true;cutOffTailIfNeeded(renderState, false);// Since nothing actually suspended, there will nothing to ping this// to get it started back up to attempt the next item. If we can show// them, then they really have the same priority as this render.// So we'll pick it back up the very next render pass once we've had// an opportunity to yield for paint.const nextPriority = renderExpirationTime - 1;workInProgress.expirationTime = workInProgress.childExpirationTime = nextPriority;if (enableSchedulerTracing) {markSpawnedWork(nextPriority);}}}if (renderState.isBackwards) {// The effect list of the backwards tail will have been added// to the end. This breaks the guarantee that life-cycles fire in// sibling order but that isn't a strong guarantee promised by React.// Especially since these might also just pop in during future commits.// Append to the beginning of the list.renderedTail.sibling = workInProgress.child;workInProgress.child = renderedTail;} else {let previousSibling = renderState.last;if (previousSibling !== null) {previousSibling.sibling = renderedTail;} else {workInProgress.child = renderedTail;}renderState.last = renderedTail;}}if (renderState.tail !== null) {// We still have tail rows to render.if (renderState.tailExpiration === 0) {// Heuristic for how long we're willing to spend rendering rows// until we just give up and show what we have so far.const TAIL_EXPIRATION_TIMEOUT_MS = 500;renderState.tailExpiration = now() + TAIL_EXPIRATION_TIMEOUT_MS;// TODO: This is meant to mimic the train model or JND but this// is a per component value. It should really be since the start// of the total render or last commit. Consider using something like// globalMostRecentFallbackTime. That doesn't account for being// suspended for part of the time or when it's a new render.// It should probably use a global start time value instead.}// Pop a row.let next = renderState.tail;renderState.rendering = next;renderState.tail = next.sibling;renderState.lastEffect = workInProgress.lastEffect;renderState.renderingStartTime = now();next.sibling = null;// Restore the context.// TODO: We can probably just avoid popping it instead and only// setting it the first time we go from not suspended to suspended.let suspenseContext = suspenseStackCursor.current;if (didSuspendAlready) {suspenseContext = setShallowSuspenseContext(suspenseContext,ForceSuspenseFallback,);} else {suspenseContext = setDefaultShallowSuspenseContext(suspenseContext);}pushSuspenseContext(workInProgress, suspenseContext);// Do a pass over the next row.return next;}return null;}case FundamentalComponent: {if (enableFundamentalAPI) {const fundamentalImpl = workInProgress.type.impl;let fundamentalInstance: ReactFundamentalComponentInstance<any,any,> | null = workInProgress.stateNode;if (fundamentalInstance === null) {const getInitialState = fundamentalImpl.getInitialState;let fundamentalState;if (getInitialState !== undefined) {fundamentalState = getInitialState(newProps);}fundamentalInstance = workInProgress.stateNode = createFundamentalStateInstance(workInProgress,newProps,fundamentalImpl,fundamentalState || {},);const instance = ((getFundamentalComponentInstance(fundamentalInstance,): any): Instance);fundamentalInstance.instance = instance;if (fundamentalImpl.reconcileChildren === false) {return null;}appendAllChildren(instance, workInProgress, false, false);mountFundamentalComponent(fundamentalInstance);} else {// We fire update in commit phaseconst prevProps = fundamentalInstance.props;fundamentalInstance.prevProps = prevProps;fundamentalInstance.props = newProps;fundamentalInstance.currentFiber = workInProgress;if (supportsPersistence) {const instance = cloneFundamentalInstance(fundamentalInstance);fundamentalInstance.instance = instance;appendAllChildren(instance, workInProgress, false, false);}const shouldUpdate = shouldUpdateFundamentalComponent(fundamentalInstance,);if (shouldUpdate) {markUpdate(workInProgress);}}return null;}break;}case ScopeComponent: {if (enableScopeAPI) {if (current === null) {const type = workInProgress.type;const scopeInstance: ReactScopeInstance = {fiber: workInProgress,methods: null,};workInProgress.stateNode = scopeInstance;scopeInstance.methods = createScopeMethods(type, scopeInstance);if (enableDeprecatedFlareAPI) {const listeners = newProps.DEPRECATED_flareListeners;if (listeners != null) {const rootContainerInstance = getRootHostContainer();updateDeprecatedEventListeners(listeners,workInProgress,rootContainerInstance,);}}if (workInProgress.ref !== null) {markRef(workInProgress);markUpdate(workInProgress);}} else {if (enableDeprecatedFlareAPI) {const prevListeners =current.memoizedProps.DEPRECATED_flareListeners;const nextListeners = newProps.DEPRECATED_flareListeners;if (prevListeners !== nextListeners ||workInProgress.ref !== null) {markUpdate(workInProgress);}} else {if (workInProgress.ref !== null) {markUpdate(workInProgress);}}if (current.ref !== workInProgress.ref) {markRef(workInProgress);}}return null;}break;}case Block:if (enableBlocksAPI) {return null;}break;}invariant(false,'Unknown unit of work tag (%s). This error is likely caused by a bug in ' +'React. Please file an issue.',workInProgress.tag,);
    }

    appendAllChildren 方法:

    // Mutation mode// 将所有子级追到到父级中appendAllChildren = function (parent: Instance,workInProgress: Fiber,needsVisibilityToggle: boolean,isHidden: boolean,) {// 获取子级let node = workInProgress.child;// 如果子级不为空 执行循环while (node !== null) {// 如果 node 是普通 ReactElement 或者为文本if (node.tag === HostComponent || node.tag === HostText) {// 将子级追加到父级中appendInitialChild(parent, node.stateNode);} else if (enableFundamentalAPI && node.tag === FundamentalComponent) {appendInitialChild(parent, node.stateNode.instance);} else if (node.tag === HostPortal) {// If we have a portal child, then we don't want to traverse// down its children. Instead, we'll get insertions from each child in// the portal directly.} else if (node.child !== null) {// 如果 node 不是普通 ReactElement 又不是文本// 将 node 视为组件, 组件本身不能转换为真实 DOM 元素// 获取到组件的第一个子元素, 继续执行循环node.child.return = node;node = node.child;continue;}// 如果 node 和 workInProgress 是同一个节点// 说明 node 已经退回到父级 终止循环// 说明此时所有子级都已经追加到父级中了if (node === workInProgress) {return;}// 处理子级节点的兄弟节点while (node.sibling === null) {// 如果节点没有父级或者节点的父级是自己, 退出循环// 说明此时所有子级都已经追加到父级中了if (node.return === null || node.return === workInProgress) {return;}// 更新 nodenode = node.return;}// 更新父级 方便回退node.sibling.return = node.return;// 将 node 更新为下一个兄弟节点node = node.sibling;}};
    
  20. completeUnitOfWork方法解析

  21. finishSyncRender 方法

    function finishSyncRender(root) {// 销毁 workInProgress Fiber 树// 因为待提交 Fiber 对象已经被存储在了 root.finishedWork 中workInProgressRoot = null;// 进入 commit 阶段commitRoot(root);
    }
    function commitRoot(root) {// 获取任务优先级 97 => 普通优先级const renderPriorityLevel = getCurrentPriorityLevel();// 使用最高优先级执行当前任务, 因为 commit 阶段不可以被打断// ImmediatePriority, 优先级为 99, 最高优先级runWithPriority(ImmediatePriority,commitRootImpl.bind(null, root, renderPriorityLevel),);return null;
    }
    1. commitRootImpl 方法

      function commitRootImpl(root, renderPriorityLevel) {do {// 触发useEffect回调与其他同步任务// 由于这些任务可能触发新的渲染// 所以这里要一直遍历执行直到没有任务flushPassiveEffects();} while (rootWithPendingPassiveEffects !== null);// 开发环境执行 忽略flushRenderPhaseStrictModeWarningsInDEV();invariant((executionContext & (RenderContext | CommitContext)) === NoContext,'Should not already be working.',);// 获取待提交 Fiber 对象 rootFiberconst finishedWork = root.finishedWork;// 1073741823const expirationTime = root.finishedExpirationTime;// 如果没有任务要执行if (finishedWork === null) {// 阻止程序继续向下执行return null;}// 重置为默认值root.finishedWork = null;root.finishedExpirationTime = NoWork;invariant(finishedWork !== root.current,'Cannot commit the same tree as before. This error is likely caused by ' +'a bug in React. Please file an issue.',);// commitRoot 是最后阶段, 不会再被异步调用了// 所以清除 callback 相关的属性root.callbackNode = null;root.callbackExpirationTime = NoWork;root.callbackPriority = NoPriority;root.nextKnownPendingLevel = NoWork;startCommitTimer();// Update the first and last pending times on this root. The new first// pending time is whatever is left on the root fiber.const remainingExpirationTimeBeforeCommit = getRemainingExpirationTime(finishedWork,);// 重置优先级相关变量markRootFinishedAtTime(root,expirationTime,remainingExpirationTimeBeforeCommit,);// falseif (root === workInProgressRoot) {// We can reset these now that they are finished.workInProgressRoot = null;workInProgress = null;renderExpirationTime = NoWork;} else {// 这表明我们处理的最后一个根与我们现在提交的根不同// 最常见的情况是在挂起的根超时时发生}// 将 effectList 赋值给 firstEffect// 由于每个 Fiber 的 effectList 只包含他的子孙节点// 所以根节点如果有 effectTag 则不会被包含进来// 所以这里将有 effectTag 的根节点插入到 effectList 尾部// 这样才能保证有 effect 的 fiber 都在 effectList 中let firstEffect;// finishedWork.effectTag => 0// PerformedWork => 1// falseif (finishedWork.effectTag > PerformedWork) {if (finishedWork.lastEffect !== null) {finishedWork.lastEffect.nextEffect = finishedWork;firstEffect = finishedWork.firstEffect;} else {firstEffect = finishedWork;}} else {// 根节点没有 effectTag// 获取要执行 DOM 操作的副作用列表firstEffect = finishedWork.firstEffect;}// 以上代码为 commit 之前所做的准备工作// firstEffect 会在 commit 的三个子阶段会用到// trueif (firstEffect !== null) {// 8const prevExecutionContext = executionContext;// 40executionContext |= CommitContext;const prevInteractions = pushInteractions(root);// Reset this to null before calling lifecyclesReactCurrentOwner.current = null;// The commit phase is broken into several sub-phases. We do a separate pass// of the effect list for each phase: all mutation effects come before all// layout effects, and so on.// The first phase a "before mutation" phase. We use this phase to read the// state of the host tree right before we mutate it. This is where// getSnapshotBeforeUpdate is called.startCommitSnapshotEffectsTimer();prepareForCommit(root.containerInfo);nextEffect = firstEffect;// commit 第一个子阶段// 处理类组件的 getSnapShotBeforeUpdate 生命周期函数do {if (__DEV__) {invokeGuardedCallback(null, commitBeforeMutationEffects, null);if (hasCaughtError()) {invariant(nextEffect !== null, 'Should be working on an effect.');const error = clearCaughtError();captureCommitPhaseError(nextEffect, error);nextEffect = nextEffect.nextEffect;}} else {try {commitBeforeMutationEffects();} catch (error) {invariant(nextEffect !== null, 'Should be working on an effect.');captureCommitPhaseError(nextEffect, error);nextEffect = nextEffect.nextEffect;}}} while (nextEffect !== null);stopCommitSnapshotEffectsTimer();if (enableProfilerTimer) {// Mark the current commit time to be shared by all Profilers in this// batch. This enables them to be grouped later.recordCommitTime();}// The next phase is the mutation phase, where we mutate the host tree.startCommitHostEffectsTimer();// commit 第二个子阶段nextEffect = firstEffect;do {if (__DEV__) {invokeGuardedCallback(null,commitMutationEffects,null,root,renderPriorityLevel,);if (hasCaughtError()) {invariant(nextEffect !== null, 'Should be working on an effect.');const error = clearCaughtError();captureCommitPhaseError(nextEffect, error);nextEffect = nextEffect.nextEffect;}} else {try {commitMutationEffects(root, renderPriorityLevel);} catch (error) {invariant(nextEffect !== null, 'Should be working on an effect.');captureCommitPhaseError(nextEffect, error);nextEffect = nextEffect.nextEffect;}}} while (nextEffect !== null);stopCommitHostEffectsTimer();resetAfterCommit(root.containerInfo);// The work-in-progress tree is now the current tree. This must come after// the mutation phase, so that the previous tree is still current during// componentWillUnmount, but before the layout phase, so that the finished// work is current during componentDidMount/Update.root.current = finishedWork;// The next phase is the layout phase, where we call effects that read// the host tree after it's been mutated. The idiomatic use case for this is// layout, but class component lifecycles also fire here for legacy reasons.startCommitLifeCyclesTimer();// commit 第三个子阶段nextEffect = firstEffect;do {if (__DEV__) {invokeGuardedCallback(null,commitLayoutEffects,null,root,expirationTime,);if (hasCaughtError()) {invariant(nextEffect !== null, 'Should be working on an effect.');const error = clearCaughtError();captureCommitPhaseError(nextEffect, error);nextEffect = nextEffect.nextEffect;}} else {try {commitLayoutEffects(root, expirationTime);} catch (error) {invariant(nextEffect !== null, 'Should be working on an effect.');captureCommitPhaseError(nextEffect, error);nextEffect = nextEffect.nextEffect;}}} while (nextEffect !== null);stopCommitLifeCyclesTimer();// 重置 nextEffectnextEffect = null;// Tell Scheduler to yield at the end of the frame, so the browser has an// opportunity to paint.requestPaint();if (enableSchedulerTracing) {popInteractions(((prevInteractions: any): Set<Interaction>));}executionContext = prevExecutionContext;} else {// No effects.root.current = finishedWork;// Measure these anyway so the flamegraph explicitly shows that there were// no effects.// TODO: Maybe there's a better way to report this.startCommitSnapshotEffectsTimer();stopCommitSnapshotEffectsTimer();if (enableProfilerTimer) {recordCommitTime();}startCommitHostEffectsTimer();stopCommitHostEffectsTimer();startCommitLifeCyclesTimer();stopCommitLifeCyclesTimer();}stopCommitTimer();const rootDidHavePassiveEffects = rootDoesHavePassiveEffects;if (rootDoesHavePassiveEffects) {// This commit has passive effects. Stash a reference to them. But don't// schedule a callback until after flushing layout work.rootDoesHavePassiveEffects = false;rootWithPendingPassiveEffects = root;pendingPassiveEffectsExpirationTime = expirationTime;pendingPassiveEffectsRenderPriority = renderPriorityLevel;} else {// We are done with the effect chain at this point so let's clear the// nextEffect pointers to assist with GC. If we have passive effects, we'll// clear this in flushPassiveEffects.nextEffect = firstEffect;while (nextEffect !== null) {const nextNextEffect = nextEffect.nextEffect;nextEffect.nextEffect = null;nextEffect = nextNextEffect;}}// Check if there's remaining work on this rootconst remainingExpirationTime = root.firstPendingTime;if (remainingExpirationTime !== NoWork) {if (enableSchedulerTracing) {if (spawnedWorkDuringRender !== null) {const expirationTimes = spawnedWorkDuringRender;spawnedWorkDuringRender = null;for (let i = 0; i < expirationTimes.length; i++) {scheduleInteractions(root,expirationTimes[i],root.memoizedInteractions,);}}schedulePendingInteractions(root, remainingExpirationTime);}} else {// If there's no remaining work, we can clear the set of already failed// error boundaries.legacyErrorBoundariesThatAlreadyFailed = null;}if (enableSchedulerTracing) {if (!rootDidHavePassiveEffects) {// If there are no passive effects, then we can complete the pending interactions.// Otherwise, we'll wait until after the passive effects are flushed.// Wait to do this until after remaining work has been scheduled,// so that we don't prematurely signal complete for interactions when there's e.g. hidden work.finishPendingInteractions(root, expirationTime);}}if (remainingExpirationTime === Sync) {// Count the number of times the root synchronously re-renders without// finishing. If there are too many, it indicates an infinite update loop.if (root === rootWithNestedUpdates) {nestedUpdateCount++;} else {nestedUpdateCount = 0;rootWithNestedUpdates = root;}} else {nestedUpdateCount = 0;}onCommitRoot(finishedWork.stateNode, expirationTime);// Always call this before exiting `commitRoot`, to ensure that any// additional work on this root is scheduled.ensureRootIsScheduled(root);if (hasUncaughtError) {hasUncaughtError = false;const error = firstUncaughtError;firstUncaughtError = null;throw error;}if ((executionContext & LegacyUnbatchedContext) !== NoContext) {// This is a legacy edge case. We just committed the initial mount of// a ReactDOM.render-ed root inside of batchedUpdates. The commit fired// synchronously, but layout updates should be deferred until the end// of the batch.return null;}// If layout work was scheduled, flush it now.flushSyncCallbackQueue();return null;
      }

      commit 阶段可以分为三个子阶段:

      - before mutation 阶段(执行 DOM 操作前)

      // commit 阶段的第一个子阶段
      // 调用类组件的 getSnapshotBeforeUpdate 生命周期函数
      function commitBeforeMutationEffects() {// 循环 effect 链while (nextEffect !== null) {// nextEffect 是 effect 链上从 firstEffect 到 lastEffect// 的每一个需要commit的 fiber 对象// 初始化渲染第一个 nextEffect 为 App 组件// effectTag => 3const effectTag = nextEffect.effectTag;// console.log(effectTag);// nextEffect = null;// return;// 如果 fiber 对象中里有 Snapshot 这个 effectTag 的话// Snapshot 和更新有关系 初始化渲染 不执行if ((effectTag & Snapshot) !== NoEffect) {// 开发环境执行 忽略setCurrentDebugFiberInDEV(nextEffect);// 计 effect 的数recordEffect();// 获取当前 fiber 节点const current = nextEffect.alternate;// 当 nextEffect 上有 Snapshot 这个 effectTag 时// 执行以下方法, 主要是类组件调用 getSnapshotBeforeUpdate 生命周期函数commitBeforeMutationEffectOnFiber(current, nextEffect);// 开发环境执行 忽略resetCurrentDebugFiberInDEV();}// 调度 useEffect// 初始化渲染 目前没有 不执行// falseif ((effectTag & Passive) !== NoEffect) {// If there are passive effects, schedule a callback to flush at// the earliest opportunity.if (!rootDoesHavePassiveEffects) {rootDoesHavePassiveEffects = true;scheduleCallback(NormalPriority, () => {// 触发useEffectflushPassiveEffects();return null;});}}nextEffect = nextEffect.nextEffect;}
      }
      
      function commitBeforeMutationLifeCycles(current: Fiber | null,finishedWork: Fiber,
      ): void {switch (finishedWork.tag) {case FunctionComponent:case ForwardRef:case SimpleMemoComponent:case Block: {return;}// 如果该 fiber 类型是 ClassComponentcase ClassComponent: {if (finishedWork.effectTag & Snapshot) {if (current !== null) {// 旧的 propsconst prevProps = current.memoizedProps;// 旧的 stateconst prevState = current.memoizedState;startPhaseTimer(finishedWork, 'getSnapshotBeforeUpdate');// 获取 classComponent 组件的实例对象const instance = finishedWork.stateNode;// We could update instance props and state here,// but instead we rely on them being set during last render.// TODO: revisit this when we implement resuming.if (__DEV__) {if (finishedWork.type === finishedWork.elementType &&!didWarnAboutReassigningProps) {if (instance.props !== finishedWork.memoizedProps) {console.error('Expected %s props to match memoized props before ' +'getSnapshotBeforeUpdate. ' +'This might either be because of a bug in React, or because ' +'a component reassigns its own `this.props`. ' +'Please file an issue.',getComponentName(finishedWork.type) || 'instance',);}if (instance.state !== finishedWork.memoizedState) {console.error('Expected %s state to match memoized state before ' +'getSnapshotBeforeUpdate. ' +'This might either be because of a bug in React, or because ' +'a component reassigns its own `this.props`. ' +'Please file an issue.',getComponentName(finishedWork.type) || 'instance',);}}}// 执行 getSnapshotBeforeUpdate 生命周期函数// 在组件更新前捕获一些 DOM 信息// 返回自定义的值或 null, 统称为 snapshotconst snapshot = instance.getSnapshotBeforeUpdate(finishedWork.elementType === finishedWork.type? prevProps: resolveDefaultProps(finishedWork.type, prevProps),prevState,);if (__DEV__) {const didWarnSet = ((didWarnAboutUndefinedSnapshotBeforeUpdate: any): Set<mixed>);if (snapshot === undefined && !didWarnSet.has(finishedWork.type)) {didWarnSet.add(finishedWork.type);console.error('%s.getSnapshotBeforeUpdate(): A snapshot value (or null) ' +'must be returned. You have returned undefined.',getComponentName(finishedWork.type),);}}// 将 snapshot 赋值到 __reactInternalSnapshotBeforeUpdate 属性上instance.__reactInternalSnapshotBeforeUpdate = snapshot;stopPhaseTimer();}}return;}case HostRoot:case HostComponent:case HostText:case HostPortal:case IncompleteClassComponent:// Nothing to do for these component typesreturn;}invariant(false,'This unit of work tag should not have side-effects. This error is ' +'likely caused by a bug in React. Please file an issue.',);
      }

      - mutation 阶段(执行 DOM 操作)

      // commit 阶段的第二个子阶段
      // 根据 effectTag 执行 DOM 操作
      function commitMutationEffects(root: FiberRoot, renderPriorityLevel) {// 循环 effect 链while (nextEffect !== null) {// 开发环境执行 忽略setCurrentDebugFiberInDEV(nextEffect);// 获取 effectTag// 初始渲染第一次循环为 App 组件// 即将根组件及内部所有内容一次性添加到页面中const effectTag = nextEffect.effectTag;// 如果有文本节点, 将 value 置为''if (effectTag & ContentReset) {commitResetTextContent(nextEffect);}// 更新 refif (effectTag & Ref) {const current = nextEffect.alternate;if (current !== null) {commitDetachRef(current);}}// 根据 effectTag 分别处理let primaryEffectTag =effectTag & (Placement | Update | Deletion | Hydrating);// 匹配 effectTag// 初始渲染 primaryEffectTag 为 2 匹配到 Placementswitch (primaryEffectTag) {// 针对该节点及子节点进行插入操作case Placement: {commitPlacement(nextEffect);// effectTag 从 3 变为 1// 从 effect 标签中清除 "placement" 重置 effectTag 值// 以便我们知道在调用诸如componentDidMount之类的任何生命周期之前已将其插入。nextEffect.effectTag &= ~Placement;break;}// 插入并更新 DOMcase PlacementAndUpdate: {// 插入commitPlacement(nextEffect);// Clear the "placement" from effect tag so that we know that this is// inserted, before any life-cycles like componentDidMount gets called.nextEffect.effectTag &= ~Placement;// 更新const current = nextEffect.alternate;commitWork(current, nextEffect);break;}// 服务器端渲染case Hydrating: {nextEffect.effectTag &= ~Hydrating;break;}// 服务器端渲染case HydratingAndUpdate: {nextEffect.effectTag &= ~Hydrating;// Updateconst current = nextEffect.alternate;commitWork(current, nextEffect);break;}// 更新 DOMcase Update: {const current = nextEffect.alternate;commitWork(current, nextEffect);break;}// 删除 DOMcase Deletion: {commitDeletion(root, nextEffect, renderPriorityLevel);break;}}// TODO: Only record a mutation effect if primaryEffectTag is non-zero.recordEffect();resetCurrentDebugFiberInDEV();nextEffect = nextEffect.nextEffect;}
      }
      
      // 挂载 DOM 元素
      function commitPlacement(finishedWork: Fiber): void {// finishedWork 初始化渲染时为根组件 Fiber 对象if (!supportsMutation) {return;}// 获取非组件父级 Fiber 对象// 初始渲染时为 <div id="root"></div>const parentFiber = getHostParentFiber(finishedWork);// 存储真正的父级 DOM 节点对象let parent;// 是否为渲染容器// 渲染容器和普通react元素的主要区别在于是否需要特殊处理注释节点let isContainer;// 获取父级 DOM 节点对象// 但是初始渲染时 rootFiber 对象中的 stateNode 存储的是 FiberRootconst parentStateNode = parentFiber.stateNode;// 判断父节点的类型// 初始渲染时是 hostRoot 3switch (parentFiber.tag) {case HostComponent:parent = parentStateNode;isContainer = false;break;case HostRoot:// 获取真正的 DOM 节点对象// <div id="root"></div>parent = parentStateNode.containerInfo;// 是 container 容器isContainer = true;break;case HostPortal:parent = parentStateNode.containerInfo;isContainer = true;break;case FundamentalComponent:if (enableFundamentalAPI) {parent = parentStateNode.instance;isContainer = false;}// eslint-disable-next-line-no-fallthroughdefault:invariant(false,'Invalid host parent fiber. This error is likely caused by a bug ' +'in React. Please file an issue.',);}// 如果父节点是文本节点的话if (parentFiber.effectTag & ContentReset) {// 在进行任何插入操作前, 需要先将 value 置为 ''resetTextContent(parent);// 清除 ContentReset 这个 effectTagparentFiber.effectTag &= ~ContentReset;}// 查看当前节点是否有下一个兄弟节点// 有, 执行 insertBefore// 没有, 执行 appendChildconst before = getHostSibling(finishedWork);// 渲染容器if (isContainer) {// 向父节点中追加节点 或者 将子节点插入到 before 节点的前面insertOrAppendPlacementNodeIntoContainer(finishedWork, before, parent);} else {// 非渲染容器// 向父节点中追加节点 或者 将子节点插入到 before 节点的前面insertOrAppendPlacementNode(finishedWork, before, parent);}
      }
      
      // 获取 HostRootFiber 对象
      function getHostParentFiber(fiber: Fiber): Fiber {// 获取当前 Fiber 父级let parent = fiber.return;// 查看父级是否为 nullwhile (parent !== null) {// 查看父级是否为 hostRootif (isHostParent(parent)) {// 返回return parent;}// 继续向上查找parent = parent.return;}invariant(false,'Expected to find a host parent. This error is likely caused by a bug ' +'in React. Please file an issue.',);
      }
      // 获取非组件父级
      function isHostParent(fiber: Fiber): boolean {return (fiber.tag === HostComponent ||fiber.tag === HostRoot ||fiber.tag === HostPortal);
      }
      
      // 向容器中追加 | 插入到某一个节点的前面
      function insertOrAppendPlacementNodeIntoContainer(node: Fiber,before: ?Instance,parent: Container,
      ): void {const {tag} = node;// 如果待插入的节点是一个 DOM 元素或者文本的话// 比如 组件fiber => false div => trueconst isHost = tag === HostComponent || tag === HostText;if (isHost || (enableFundamentalAPI && tag === FundamentalComponent)) {// 获取 DOM 节点const stateNode = isHost ? node.stateNode : node.stateNode.instance;// 如果 before 存在if (before) {// 插入到 before 前面insertInContainerBefore(parent, stateNode, before);} else {// 追加到父容器中appendChildToContainer(parent, stateNode);}} else if (tag === HostPortal) {// If the insertion itself is a portal, then we don't want to traverse// down its children. Instead, we'll get insertions from each child in// the portal directly.} else {// 如果是组件节点, 比如 ClassComponent, 则找它的第一个子节点(DOM 元素)// 进行插入操作const child = node.child;if (child !== null) {// 向父级中追加子节点或者将子节点插入到 before 的前面insertOrAppendPlacementNodeIntoContainer(child, before, parent);// 获取下一个兄弟节点let sibling = child.sibling;// 如果兄弟节点存在while (sibling !== null) {// 向父级中追加子节点或者将子节点插入到 before 的前面insertOrAppendPlacementNodeIntoContainer(sibling, before, parent);// 同步兄弟节点sibling = sibling.sibling;}}}
      }
      function insertOrAppendPlacementNode(node: Fiber,before: ?Instance,parent: Instance,
      ): void {const {tag} = node;const isHost = tag === HostComponent || tag === HostText;if (isHost || (enableFundamentalAPI && tag === FundamentalComponent)) {const stateNode = isHost ? node.stateNode : node.stateNode.instance;if (before) {insertBefore(parent, stateNode, before);} else {appendChild(parent, stateNode);}} else if (tag === HostPortal) {// If the insertion itself is a portal, then we don't want to traverse// down its children. Instead, we'll get insertions from each child in// the portal directly.} else {const child = node.child;if (child !== null) {insertOrAppendPlacementNode(child, before, parent);let sibling = child.sibling;while (sibling !== null) {insertOrAppendPlacementNode(sibling, before, parent);sibling = sibling.sibling;}}}
      }
      
      
      export function insertInContainerBefore(container: Container,child: Instance | TextInstance,beforeChild: Instance | TextInstance | SuspenseInstance,
      ): void {// 如果父容器是注释节点if (container.nodeType === COMMENT_NODE) {// 找到注释节点的父级节点 因为注释节点没法调用 insertBefore(container.parentNode: any).insertBefore(child, beforeChild);} else {// 将 child 插入到 beforeChild 的前面container.insertBefore(child, beforeChild);}
      }
      
      export function appendChildToContainer(container: Container,child: Instance | TextInstance,
      ): void {let parentNode;// 监测 container 是否注释节点if (container.nodeType === COMMENT_NODE) {// 获取父级的父级parentNode = (container.parentNode: any);// 将子级节点插入到注释节点的前面parentNode.insertBefore(child, container);} else {// 直接将 child 插入到父级中parentNode = container;parentNode.appendChild(child);}// This container might be used for a portal.// If something inside a portal is clicked, that click should bubble// through the React tree. However, on Mobile Safari the click would// never bubble through the *DOM* tree unless an ancestor with onclick// event exists. So we wouldn't see it and dispatch it.// This is why we ensure that non React root containers have inline onclick// defined.// https://github.com/facebook/react/issues/11918const reactRootContainer = container._reactRootContainer;if ((reactRootContainer === null || reactRootContainer === undefined) &&parentNode.onclick === null) {// TODO: This cast may not be sound for SVG, MathML or custom elements.trapClickOnNonInteractiveElement(((parentNode: any): HTMLElement));}
      }
      

      - layout 阶段(执行 DOM 操作后)

      // commit 阶段的第三个子阶段
      function commitLayoutEffects(root: FiberRoot,committedExpirationTime: ExpirationTime,
      ) {while (nextEffect !== null) {setCurrentDebugFiberInDEV(nextEffect);// 此时 effectTag 已经被重置为 1, 表示 DOM 操作已经完成const effectTag = nextEffect.effectTag;// 调用生命周期函数和钩子函数// 前提是类组件中调用了生命周期函数// 或者函数组件中调用了 useEffectif (effectTag & (Update | Callback)) {recordEffect();const current = nextEffect.alternate;// 类组件处理生命周期函数// 函数组件处理钩子函数commitLayoutEffectOnFiber(root,current,nextEffect,committedExpirationTime,);}// 赋值ref// falseif (effectTag & Ref) {recordEffect();commitAttachRef(nextEffect);}resetCurrentDebugFiberInDEV();// 更新循环条件nextEffect = nextEffect.nextEffect;}
      }

      commitLayoutEffectOnFiber 方法实际上指向的是commitLifeCycles 方法

      function commitLifeCycles(finishedRoot: FiberRoot,current: Fiber | null,finishedWork: Fiber,committedExpirationTime: ExpirationTime,
      ): void {switch (finishedWork.tag) {case FunctionComponent:case ForwardRef:case SimpleMemoComponent:case Block: {// At this point layout effects have already been destroyed (during mutation phase).// This is done to prevent sibling component effects from interfering with each other,// e.g. a destroy function in one component should never override a ref set// by a create function in another component during the same commit.commitHookEffectListMount(HookLayout | HookHasEffect, finishedWork);if (runAllPassiveEffectDestroysBeforeCreates) {schedulePassiveEffects(finishedWork);}return;}case ClassComponent: {// 获取类组件实例对象const instance = finishedWork.stateNode;// 如果在类组件中存在生命周期函数判断条件就会成立if (finishedWork.effectTag & Update) {// 初始渲染阶段if (current === null) {startPhaseTimer(finishedWork, 'componentDidMount');if (__DEV__) {if (finishedWork.type === finishedWork.elementType &&!didWarnAboutReassigningProps) {if (instance.props !== finishedWork.memoizedProps) {console.error('Expected %s props to match memoized props before ' +'componentDidMount. ' +'This might either be because of a bug in React, or because ' +'a component reassigns its own `this.props`. ' +'Please file an issue.',getComponentName(finishedWork.type) || 'instance',);}if (instance.state !== finishedWork.memoizedState) {console.error('Expected %s state to match memoized state before ' +'componentDidMount. ' +'This might either be because of a bug in React, or because ' +'a component reassigns its own `this.props`. ' +'Please file an issue.',getComponentName(finishedWork.type) || 'instance',);}}}// 调用 componentDidMount 生命周期函数instance.componentDidMount();stopPhaseTimer();} else {// 更新阶段// 获取旧的 propsconst prevProps =finishedWork.elementType === finishedWork.type? current.memoizedProps: resolveDefaultProps(finishedWork.type, current.memoizedProps);// 获取旧的 stateconst prevState = current.memoizedState;startPhaseTimer(finishedWork, 'componentDidUpdate');if (__DEV__) {if (finishedWork.type === finishedWork.elementType &&!didWarnAboutReassigningProps) {if (instance.props !== finishedWork.memoizedProps) {console.error('Expected %s props to match memoized props before ' +'componentDidUpdate. ' +'This might either be because of a bug in React, or because ' +'a component reassigns its own `this.props`. ' +'Please file an issue.',getComponentName(finishedWork.type) || 'instance',);}if (instance.state !== finishedWork.memoizedState) {console.error('Expected %s state to match memoized state before ' +'componentDidUpdate. ' +'This might either be because of a bug in React, or because ' +'a component reassigns its own `this.props`. ' +'Please file an issue.',getComponentName(finishedWork.type) || 'instance',);}}}// 调用 componentDidUpdate 生命周期函数// instance.__reactInternalSnapshotBeforeUpdate 快照// getSnapShotBeforeUpdate 方法的返回值instance.componentDidUpdate(prevProps,prevState,instance.__reactInternalSnapshotBeforeUpdate,);stopPhaseTimer();}}// 获取任务队列const updateQueue = finishedWork.updateQueue;// 如果任务队列存在if (updateQueue !== null) {if (__DEV__) {if (finishedWork.type === finishedWork.elementType &&!didWarnAboutReassigningProps) {if (instance.props !== finishedWork.memoizedProps) {console.error('Expected %s props to match memoized props before ' +'processing the update queue. ' +'This might either be because of a bug in React, or because ' +'a component reassigns its own `this.props`. ' +'Please file an issue.',getComponentName(finishedWork.type) || 'instance',);}if (instance.state !== finishedWork.memoizedState) {console.error('Expected %s state to match memoized state before ' +'processing the update queue. ' +'This might either be because of a bug in React, or because ' +'a component reassigns its own `this.props`. ' +'Please file an issue.',getComponentName(finishedWork.type) || 'instance',);}}}/*** 调用 ReactElement 渲染完成之后的回调函数* 即 render 方法的第三个参数*/commitUpdateQueue(finishedWork, updateQueue, instance);}return;}case HostRoot: {const updateQueue = finishedWork.updateQueue;if (updateQueue !== null) {let instance = null;if (finishedWork.child !== null) {switch (finishedWork.child.tag) {case HostComponent:instance = getPublicInstance(finishedWork.child.stateNode);break;case ClassComponent:instance = finishedWork.child.stateNode;break;}}commitUpdateQueue(finishedWork, updateQueue, instance);}return;}case HostComponent: {const instance: Instance = finishedWork.stateNode;// Renderers may schedule work to be done after host components are mounted// (eg DOM renderer may schedule auto-focus for inputs and form controls).// These effects should only be committed when components are first mounted,// aka when there is no current/alternate.if (current === null && finishedWork.effectTag & Update) {const type = finishedWork.type;const props = finishedWork.memoizedProps;commitMount(instance, type, props, finishedWork);}return;}case HostText: {// We have no life-cycles associated with text.return;}case HostPortal: {// We have no life-cycles associated with portals.return;}case Profiler: {if (enableProfilerTimer) {const onRender = finishedWork.memoizedProps.onRender;if (typeof onRender === 'function') {if (enableSchedulerTracing) {onRender(finishedWork.memoizedProps.id,current === null ? 'mount' : 'update',finishedWork.actualDuration,finishedWork.treeBaseDuration,finishedWork.actualStartTime,getCommitTime(),finishedRoot.memoizedInteractions,);} else {onRender(finishedWork.memoizedProps.id,current === null ? 'mount' : 'update',finishedWork.actualDuration,finishedWork.treeBaseDuration,finishedWork.actualStartTime,getCommitTime(),);}}}return;}case SuspenseComponent: {commitSuspenseHydrationCallbacks(finishedRoot, finishedWork);return;}case SuspenseListComponent:case IncompleteClassComponent:case FundamentalComponent:case ScopeComponent:return;}invariant(false,'This unit of work tag should not have side-effects. This error is ' +'likely caused by a bug in React. Please file an issue.',);
      }
      

      commitUpdateQueue 方法

      /*** 执行渲染完成之后的回调函数*/
      export function commitUpdateQueue<State>(finishedWork: Fiber,finishedQueue: UpdateQueue<State>,instance: any,
      ): void {// effects 为数组, 存储任务对象 (Update 对象)// 但前提是在调用 render 方法时传递了回调函数, 就是 render 方法的第三个参数const effects = finishedQueue.effects;// 重置 finishedQueue.effects 数组finishedQueue.effects = null;// 如果传递了 render 方法的第三个参数, effect 数组就不会为 nullif (effects !== null) {// 遍历 effect 数组for (let i = 0; i < effects.length; i++) {// 获取数组中的第 i 个需要执行的 effectconst effect = effects[i];// 获取 callback 回调函数const callback = effect.callback;// 如果回调函数不为 nullif (callback !== null) {// 清空 effect 中的 callbackeffect.callback = null;// 执行回调函数callCallback(callback, instance);}}}
      }

      callCallback 方法

      
      function callCallback(callback, context) {invariant(typeof callback === 'function','Invalid argument passed as callback. Expected a function. Instead ' +'received: %s',callback,);callback.call(context);
      }

      函数组件回调事的调用方法 commitHookEffectListMount 方法

      /*** useEffect 回调函数调用*/
      function commitHookEffectListMount(tag: number, finishedWork: Fiber) {// 获取任务队列const updateQueue: FunctionComponentUpdateQueue | null = (finishedWork.updateQueue: any);// 获取 lastEffectlet lastEffect = updateQueue !== null ? updateQueue.lastEffect : null;// 如果 lastEffect 不为 nullif (lastEffect !== null) {// 获取要执行的副作用const firstEffect = lastEffect.next;let effect = firstEffect;// 通过遍历的方式调用 useEffect 中的回调函数// 在组件中定义了调用了几次 useEffect 遍历就会执行几次do {if ((effect.tag & tag) === tag) {// Mountconst create = effect.create;// create 就是 useEffect 方法的第一个参数// 返回值就是清理函数effect.destroy = create();if (__DEV__) {const destroy = effect.destroy;if (destroy !== undefined && typeof destroy !== 'function') {let addendum;if (destroy === null) {addendum =' You returned null. If your effect does not require clean ' +'up, return undefined (or nothing).';} else if (typeof destroy.then === 'function') {addendum ='\n\nIt looks like you wrote useEffect(async () => ...) or returned a Promise. ' +'Instead, write the async function inside your effect ' +'and call it immediately:\n\n' +'useEffect(() => {\n' +'  async function fetchData() {\n' +'    // You can await here\n' +'    const response = await MyAPI.getData(someId);\n' +'    // ...\n' +'  }\n' +'  fetchData();\n' +`}, [someId]); // Or [] if effect doesn't need props or state\n\n` +'Learn more about data fetching with Hooks: https://fb.me/react-hooks-data-fetching';} else {addendum = ' You returned: ' + destroy;}console.error('An effect function must not return anything besides a function, ' +'which is used for clean-up.%s%s',addendum,getStackByFiberInDevAndProd(finishedWork),);}}}// 更新循环条件effect = effect.next;} while (effect !== firstEffect);}
      }

React源码解毒 - render方法解析相关推荐

  1. React源码解毒 - 检测开发者是否错误的使用了props属性

    说明: 在使用react.createElement()创建react 元素的时候,如果是开发环境中,错误地使用了props属性对key属性或者ref属性进行了访问,会在控制台中提示相应的错误. // ...

  2. react源码解析之stack reconciler

    关于源码解读的系列文章,可以关注我的github的这个仓库, 现在才刚刚写,后续有空就写点.争取把react源码剖析透学习透.有不正确的地方希望大家帮忙指正.大家互相学习,共同进步. 本篇文章是官方文 ...

  3. 《React源码解析》系列完结!

    前言 距离第一篇<React源码解析(一)>已经过去将近4个月的时间,由于是我第一次进行源码解析相关的写作,思路和文笔还不够成熟.一百多天以来,我基于读者反馈反思这几篇文章中的不足,同时也 ...

  4. 人人都能读懂的react源码解析(大厂高薪必备)

    人人都能读懂的react源码解析(大厂高薪必备) 1.开篇(听说你还在艰难的啃react源码) ​ 本教程目标是打造一门严谨(严格遵循react17核心思想).通俗易懂(提供大量流程图解,结合demo ...

  5. react源码解析15.schedulerLane

    react源码解析15.scheduler&Lane 视频讲解(高效学习):进入学习 往期文章: 1.开篇介绍和面试题 2.react的设计理念 3.react源码架构 4.源码目录结构和调试 ...

  6. # React源码解析之fiber的初次渲染与更新(下)

    React源码解析之fiber的初次渲染与更新(下) 经历一个月的学习整理,站在前人的肩膀上,对React有了一些浅薄的理解,希望记录自己的学习过程的同时也可以给大家带来一点小帮助.如果此系列文章对您 ...

  7. React源码分析与实现(一):组件的初始化与渲染

    原文链接地址:github.com/Nealyang 转载请注明出处 前言 战战兢兢写下开篇...也感谢小蘑菇大神以及网上各路大神的博客资料参考~ 阅读源码的方式有很多种,广度优先法.调用栈调试法等等 ...

  8. React 源码系列 | React Context 详解

    目前来看 Context 是一个非常强大但是很多时候不会直接使用的 api.大多数项目不会直接使用 createContext 然后向下面传递数据,而是采用第三方库(react-redux). 想想项 ...

  9. react源码中的fiber架构

    先看一下FiberNode在源码中的样子 FiberNode // packages/react-reconciler/src/ReactFiber.old.js function FiberNode ...

最新文章

  1. python关闭浏览器、未过期的session_session.cookie_lifetime = 0时,为什么会话在浏览器关闭时不会过期?...
  2. 轩逸车联网功能怎么用_手机上面的NFC功能怎么用的
  3. 测试电视是不是4k的软件,怎么判断4K电视真假?教你快速检测的方法!
  4. AIX逻辑卷管理(LVM)
  5. 并查集+基础知识点详解
  6. P7046-「MCOI-03」诗韵【SAM,倍增,树状数组】
  7. 痞子衡嵌入式:飞思卡尔i.MX RTyyyy系列MCU特性介绍(3)- 命名规则
  8. typedef让p去除了普通变量的C++身份
  9. unity最基本操作
  10. 不刷新页面的tab_现代 Web 页面开发流程
  11. mysql5.7tps_MySQL 5.7和8.0性能测试
  12. PHP中表单没有问题但是$_FILES为空的解决办法
  13. [读码时间] css函数设置读取对象的属性
  14. linux加速度传感器校准,加速度传感器校准方法及装置与流程
  15. 那些年的草根站长,都被时代淘汰了吗?
  16. android ios emoji兼容,web端怎么和移动端emoji表情兼容
  17. 奶茶创业者,他打造了一间港式饮品专门店
  18. android 录制视频清晰度问题
  19. 如何在latex中使用python及pythontex功能简介
  20. Python 读pdf数据写入Excel表中

热门文章

  1. CraftManager for Mac(PS/sketch自动填充神器)特别版
  2. 为什么我们要学操作系统?
  3. vSphere6.7中WindowsServer2012r2虚拟机磁盘扩容
  4. ubuntu 16.04 内存清理方法总结
  5. 阿里云短信功能网址链接
  6. 西安财经大学计算机考研科目,西安财经大学2020年硕士研究生考试复试科目与参考书目...
  7. python微信发红包看照片_微信发原图会泄露位置信息?用Python教你通过图片获取用户信息!...
  8. 福利群怎么引流?微信群引流技巧
  9. hp服务器通过ilo5安装系统,HPE ProLiant Gen10 通过iLO 5(v1.15) web界面多种方式更新服务器固件,包含升级系统恢复集方法...
  10. docker单独挂盘步骤