React 16.8.0 初次渲染 源码分析(一)
ReactDOM.render(<List />,document.getElementById('container'));
以这个demo作为例子,ReactDOM.render 作为入口
- packages/react-dom/src/ReactDOM.js
render( element: React$Element<any>, //需要渲染的组件List container: DOMContainer, // document.getElementById('container') callback: ?Function, // undefined ) { invariant( isValidContainer(container), 'Target container is not a DOM element.', ); return legacyRenderSubtreeIntoContainer( null, element, container, false, callback, ); }
render 跳转到 legacyRenderSubtreeIntoContainer
function legacyRenderSubtreeIntoContainer( parentComponent: ?React$Component<any, any>, // null children: ReactNodeList, //需要渲染的组件List container: DOMContainer, // document.getElementById('container') forceHydrate: boolean, // false callback: ?Function, // undefined ) { // TODO: Without `any` type, Flow says "Property cannot be accessed on any // member of intersection type." Whyyyyyy. let root: Root = (container._reactRootContainer: any); if (!root) { // Initial mount 初始化容器 root = container._reactRootContainer = legacyCreateRootFromDOMContainer( container, forceHydrate, );
legacyRenderSubtreeIntoContainer 先暂停到初始化容器处,看下 legacyCreateRootFromDOMContainer 如何创建 root
function legacyCreateRootFromDOMContainer( container: DOMContainer, // document.getElementById('container') forceHydrate: boolean, //false ): Root { const shouldHydrate = forceHydrate || shouldHydrateDueToLegacyHeuristic(container); // First clear any existing content. // 首次初始化清空容器 if (!shouldHydrate) { let warned = false; let rootSibling; while ((rootSibling = container.lastChild)) { container.removeChild(rootSibling); } } // Legacy roots are not async by default. const isConcurrent = false; return new ReactRoot(container, isConcurrent, shouldHydrate); }
首次初始化清空 document.getElementById('container') 容器,后 return new ReactRoot
function ReactRoot( container: DOMContainer, // document.getElementById('container') isConcurrent: boolean, //false hydrate: boolean, //false ) { const root = createContainer(container, isConcurrent, hydrate); this._internalRoot = root; }
- packages/react-reconciler/src/ReactFiberReconciler.js
export function createContainer( containerInfo: Container, // document.getElementById('container') isConcurrent: boolean, //false hydrate: boolean, //false ): OpaqueRoot { return createFiberRoot(containerInfo, isConcurrent, hydrate); }
- packages/react-reconciler/src/ReactFiberRoot.js
export function createFiberRoot( containerInfo: any, // document.getElementById('container') isConcurrent: boolean, //false hydrate: boolean, //false ): FiberRoot { // Cyclic construction. This cheats the type system right now because // stateNode is any. const uninitializedFiber = createHostRootFiber(isConcurrent);
- packages/react-reconciler/src/ReactFiber.js
export function createHostRootFiber(isConcurrent: boolean): Fiber { // isConcurrent = false let mode = isConcurrent ? ConcurrentMode | StrictMode : NoContext; if (enableProfilerTimer && isDevToolsPresent) { // Always collect profile timings when DevTools are present. // This enables DevTools to start capturing timing at any point– // Without some nodes in the tree having empty base times. mode |= ProfileMode; } // mode = NoContext return createFiber(HostRoot, null, null, mode); }
const createFiber = function( tag: WorkTag, // HostRoot = 3 pendingProps: mixed, // null key: null | string, // null mode: TypeOfMode, // NoContext = 0 ): Fiber { // $FlowFixMe: the shapes are exact here but Flow doesn't like constructors return new FiberNode(tag, pendingProps, key, mode); };
function FiberNode( tag: WorkTag, // HostRoot = 3 pendingProps: mixed, // null key: null | string, // null mode: TypeOfMode, // NoContext = 0 ) { // Instance this.tag = tag; // HostRoot = 3 this.key = key; this.elementType = null; this.type = null; this.stateNode = null; // Fiber this.return = null; this.child = null; this.sibling = null; this.index = 0; this.ref = null; this.pendingProps = pendingProps; this.memoizedProps = null; this.updateQueue = null; this.memoizedState = null; this.contextDependencies = null; this.mode = mode; // NoContext = 0 // Effects this.effectTag = NoEffect; // 0 this.nextEffect = null; this.firstEffect = null; this.lastEffect = null; this.expirationTime = NoWork; // 0 this.childExpirationTime = NoWork; // 0 this.alternate = null; if (enableProfilerTimer) { // Note: The following is done to avoid a v8 performance cliff. // // Initializing the fields below to smis and later updating them with // double values will cause Fibers to end up having separate shapes. // This behavior/bug has something to do with Object.preventExtension(). // Fortunately this only impacts DEV builds. // Unfortunately it makes React unusably slow for some applications. // To work around this, initialize the fields below with doubles. // // Learn more about this here: // https://github.com/facebook/react/issues/14365 // https://bugs.chromium.org/p/v8/issues/detail?id=8538 this.actualDuration = Number.NaN; this.actualStartTime = Number.NaN; this.selfBaseDuration = Number.NaN; this.treeBaseDuration = Number.NaN; // It's okay to replace the initial doubles with smis after initialization. // This won't trigger the performance cliff mentioned above, // and it simplifies other profiler code (including DevTools). this.actualDuration = 0; this.actualStartTime = -1; this.selfBaseDuration = 0; this.treeBaseDuration = 0; } }
上接createFiberRoot-1
let root; if (enableSchedulerTracing) { root = ({ current: uninitializedFiber, containerInfo: containerInfo, pendingChildren: null, earliestPendingTime: NoWork, latestPendingTime: NoWork, earliestSuspendedTime: NoWork, latestSuspendedTime: NoWork, latestPingedTime: NoWork, pingCache: null, didError: false, pendingCommitExpirationTime: NoWork, finishedWork: null, timeoutHandle: noTimeout, context: null, pendingContext: null, hydrate, nextExpirationTimeToWorkOn: NoWork, expirationTime: NoWork, firstBatch: null, nextScheduledRoot: null, interactionThreadID: unstable_getThreadID(), memoizedInteractions: new Set(), pendingInteractionMap: new Map(), }: FiberRoot); } else { root = ({ current: uninitializedFiber, containerInfo: containerInfo, pendingChildren: null, pingCache: null, earliestPendingTime: NoWork, latestPendingTime: NoWork, earliestSuspendedTime: NoWork, latestSuspendedTime: NoWork, latestPingedTime: NoWork, didError: false, pendingCommitExpirationTime: NoWork, finishedWork: null, timeoutHandle: noTimeout, context: null, pendingContext: null, hydrate, nextExpirationTimeToWorkOn: NoWork, expirationTime: NoWork, firstBatch: null, nextScheduledRoot: null, }: BaseFiberRootProperties); } uninitializedFiber.stateNode = root; // The reason for the way the Flow types are structured in this file, // Is to avoid needing :any casts everywhere interaction tracing fields are used. // Unfortunately that requires an :any cast for non-interaction tracing capable builds. // $FlowFixMe Remove this :any cast and replace it with something better. return ((root: any): FiberRoot); }
上接legacyCreateRootFromDOMContainer ,暂时跳过 unbatchedUpdates 方法,直接看 return getPublicRootInstance(root._internalRoot);
if (typeof callback === 'function') { const originalCallback = callback; callback = function() { const instance = getPublicRootInstance(root._internalRoot); originalCallback.call(instance); }; } // Initial mount should not be batched. unbatchedUpdates(() => { if (parentComponent != null) { root.legacy_renderSubtreeIntoContainer( parentComponent, children, callback, ); } else { root.render(children, callback); } }); } else { if (typeof callback === 'function') { const originalCallback = callback; callback = function() { const instance = getPublicRootInstance(root._internalRoot); originalCallback.call(instance); }; } // Update if (parentComponent != null) { root.legacy_renderSubtreeIntoContainer( parentComponent, children, callback, ); } else { root.render(children, callback); } } return getPublicRootInstance(root._internalRoot); }
root._internalRoot 就是 createFiberRoot 创建的 FiberRoot 对象
- packages/react-reconciler/srcReactFiberReconciler.js
export function getPublicRootInstance( container: OpaqueRoot, ): React$Component<any, any> | PublicInstance | null { const containerFiber = container.current; if (!containerFiber.child) { return null; } switch (containerFiber.child.tag) { case HostComponent: return getPublicInstance(containerFiber.child.stateNode); default: return containerFiber.child.stateNode; } }
container.current 就是 createHostRootFiber 创建的 FiberNode 对象
containerFiber.child 是 List 渲染后的FiberNode 对象,所以最终 return containerFiber.child.stateNode
小结
- legacyCreateRootFromDOMContainer函数 unbatchedUpdates 是初次渲染主要逻辑触发点。
- React在初次渲染时 在 container Dom 下注入了一个属性_reactRootContainer 的 ReactRoot 对象,作为React实例的容器
- ReactRoot 对象下注入了一个属性 _internalRoot 属性的 FiberRoot 对象,作为Fiber的根节点