React源码解析——创建更新过程
一、ReactDOM.render
创建ReactRoot
,并且根据情况调用root.legacy_renderSubtreeIntoContainer
或者root.render
,前者是遗留的 API 将来应该会删除,根据ReactDOM.render
的调用情况也可以发现parentComponent
是写死的null
DOMRenderer.unbatchedUpdates
制定不使用batchedUpdates
,因为这是初次渲染,需要尽快完成。
hydrate(element: React$Node, container: DOMContainer, callback: ?Function) { // TODO: throw or warn if we couldn't hydrate? return legacyRenderSubtreeIntoContainer( null, element, container, true, callback, ); }, render( element: React$Element<any>, container: DOMContainer, callback: ?Function, ) { return legacyRenderSubtreeIntoContainer( null, element, container, false, callback, ); },
function legacyRenderSubtreeIntoContainer( parentComponent: ?React$Component<any, any>, children: ReactNodeList, container: DOMContainer, forceHydrate: boolean, callback: ?Function, ) { let root: Root = (container._reactRootContainer: any); if (!root) { // Initial mount root = container._reactRootContainer = legacyCreateRootFromDOMContainer( container, forceHydrate, ); if (typeof callback === 'function') { const originalCallback = callback; callback = function() { const instance = DOMRenderer.getPublicRootInstance(root._internalRoot); originalCallback.call(instance); }; } // Initial mount should not be batched. DOMRenderer.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 = DOMRenderer.getPublicRootInstance(root._internalRoot); originalCallback.call(instance); }; } // Update if (parentComponent != null) { root.legacy_renderSubtreeIntoContainer( parentComponent, children, callback, ); } else { root.render(children, callback); } } return DOMRenderer.getPublicRootInstance(root._internalRoot); }
首先会创建ReactRoot
对象,然后调用他的render
方法
创建ReactRoot
的时候会调用DOMRenderer.createContainer
创建FiberRoot
,在后期调度更新的过程中这个节点非常重要
function legacyCreateRootFromDOMContainer( container: DOMContainer, forceHydrate: boolean, ): Root { const shouldHydrate = forceHydrate || shouldHydrateDueToLegacyHeuristic(container); // First clear any existing content. if (!shouldHydrate) { let warned = false; let rootSibling; while ((rootSibling = container.lastChild)) { if (__DEV__) { if ( !warned && rootSibling.nodeType === ELEMENT_NODE && (rootSibling: any).hasAttribute(ROOT_ATTRIBUTE_NAME) ) { warned = true; warningWithoutStack( false, '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.removeChild(rootSibling); } } if (__DEV__) { if (shouldHydrate && !forceHydrate && !warnedAboutHydrateAPI) { warnedAboutHydrateAPI = true; lowPriorityWarning( false, '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.', ); } } // Legacy roots are not async by default. const isConcurrent = false; return new ReactRoot(container, isConcurrent, shouldHydrate); }
export function createContainer( containerInfo: Container, isConcurrent: boolean, hydrate: boolean, ): OpaqueRoot { return createFiberRoot(containerInfo, isConcurrent, hydrate); }
二、启动调度
首先要生成一个update
,不管你是setState
还是ReactDOM.render
造成的 React 更新,都会生成一个叫update
的对象,并且会赋值给Fiber.updateQueue
然后就是调用scheduleWork
。注意到这里之前setState
和ReactDOM.render
是不一样,但进入schedulerWork
之后,就是任务调度的事情了,跟之前你是怎么调用的没有任何关系
function scheduleWork(fiber: Fiber, expirationTime: ExpirationTime) { const root = scheduleWorkToRoot(fiber, expirationTime); if (root === null) { return; } if ( !isWorking && nextRenderExpirationTime !== NoWork && expirationTime < nextRenderExpirationTime ) { // This is an interruption. (Used for performance tracking.) interruptedBy = fiber; resetStack(); } markPendingPriorityLevel(root, expirationTime); if ( // If we're in the render phase, we don't need to schedule this root // for an update, because we'll do it before we exit... !isWorking || isCommitting || // ...unless this is a different root than the one we're rendering. nextRoot !== root ) { const rootExpirationTime = root.expirationTime; requestWork(root, rootExpirationTime); } if (nestedUpdateCount > NESTED_UPDATE_LIMIT) { // Reset this back to zero so subsequent updates don't throw. nestedUpdateCount = 0; invariant( false, 'Maximum update depth exceeded. This can happen when a ' + 'component repeatedly calls setState inside ' + 'componentWillUpdate or componentDidUpdate. React limits ' + 'the number of nested updates to prevent infinite loops.', ); } }