ReactDOM.render在react源码中执行原理
flyzz177 人气:0ReactDOM.render
通常是如下图使用,在提供的 container 里渲染一个 React 元素,并返回对该组件的引用(或者针对无状态组件返回 null)。
本文主要是将ReactDOM.render的执行流程在后续文章中会对创建更新的细节进行分析,文中的源代码部分为了方便阅读将__DEV__
部分的代码移除掉了。
ReactDOM.render( <App />, document.getElementById('root') );
render
位于:react-dom/src/client/ReactDOMLegacy.js
export function render( element: React$Element<any>, container: Container, callback: ?Function, ) { // 验证container是否为有效的DOM节点 invariant( isValidContainer(container), 'Target container is not a DOM element.', ); return legacyRenderSubtreeIntoContainer( null, element, container, false, callback, ); }
返回了一个legacyRenderSubtreeIntoContainer函数,这里注意有5个参数
parentComponent
: 父组件因为是初次创建所以为null。
children
: 传入的ReactElement
container
: 渲染React的DOM容器
forceHydrate
: 判断是否需要协调,在服务端渲染的情况下已渲染的DOM结构是类似的因此可以在对比后进行复用。在服务端渲染的情况下使用ReactDOM.hydrate()与 render() 相同只是forceHydrate会标记为true。
callback
: 渲染完成后的回调函数
legacyRenderSubtreeIntoContainer
位于:react-dom/src/client/ReactDOMLegacy.js
作用:
- 判断是否为初次渲染,如果是就创建root并将root._internalRoot赋值给fiberRoot同时封装callback回调,然后调用unbatchedUpdates立即更新子节点。
- 如果不是第一次渲染则进入正常的updateContainer流程。
- 最后getPublicRootInstance(fiberRoot)返回公开的 Root 实例对象。
function legacyRenderSubtreeIntoContainer( parentComponent: ?React$Component<any, any>, children: ReactNodeList, container: Container, forceHydrate: boolean, callback: ?Function, ) { // TODO: Without `any` type, Flow says "Property cannot be accessed on any // member of intersection type." Whyyyyyy. let root: RootType = (container._reactRootContainer: any); let fiberRoot; if (!root) { // Initial mount 初次渲染创建FiberRoot root = container._reactRootContainer = legacyCreateRootFromDOMContainer( container, forceHydrate, ); fiberRoot = root._internalRoot; if (typeof callback === 'function') { const originalCallback = callback; callback = function() { const instance = getPublicRootInstance(fiberRoot); originalCallback.call(instance); }; } // Initial mount should not be batched. 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); }; } // Update updateContainer(children, fiberRoot, parentComponent, callback); } return getPublicRootInstance(fiberRoot); }
legacyCreateRootFromDOMContainer
位于:react-dom/src/client/ReactDOMLegacy.js
初次渲染进入创建root的环节:root = container._reactRootContainer = legacyCreateRootFromDOMContainer(container, forceHydrate)作用:主要是判断是否为服务端渲染,如果是的话就会复用存在的dom节点进行协调(reconciliation)提高性能,如果不是则会清空container中的子元素,最后传入container和shouldHydrate返回createLegacyRoot函数。
function legacyCreateRootFromDOMContainer( container: Container, forceHydrate: boolean, ): RootType { const shouldHydrate = forceHydrate || shouldHydrateDueToLegacyHeuristic(container); // 判断是否是服务端渲染 // First clear any existing content. if (!shouldHydrate) { let warned = false; let rootSibling; while ((rootSibling = container.lastChild)) { container.removeChild(rootSibling); } } return createLegacyRoot( container, shouldHydrate ? { hydrate: true, } : undefined, ); }
createLegacyRoot
位于:react-dom/src/client/ReactDOMRoot.js
作用:返回了一个ReactDOMBlockingRoot实例,这里传入了LegacyRoot是一个常量=0代表着现在使用的同步渲染模式,是为了后续的Concurrent可中断渲染模式做准备。
export function createLegacyRoot( container: Container, options?: RootOptions, // hydrate ): RootType { return new ReactDOMBlockingRoot(container, LegacyRoot, options); }
ReactDOMBlockingRoot
位于:react-dom/src/client/ReactDOMRoot.js
作用:将createRootImpl
函数的返回(FiberRoot)挂载到实例的_internalRoot上
function ReactDOMBlockingRoot( container: Container, tag: RootTag, options: void | RootOptions, ) { this._internalRoot = createRootImpl(container, tag, options); }
createRootImpl
位于:react-dom/src/client/ReactDOMRoot.js
作用:执行createContainer拿到FiberRootNode并赋值给root,再通过markContainerAsRoot将RootFiber挂载到container上。
function createRootImpl( container: Container, tag: RootTag, options: void | RootOptions, ) { // Tag is either LegacyRoot or Concurrent Root const hydrate = options != null && options.hydrate === true; const hydrationCallbacks = (options != null && options.hydrationOptions) || null; // 拿到FiberRootNode const root = createContainer(container, tag, hydrate, hydrationCallbacks); // 将FiberRootNode挂载到container markContainerAsRoot(root.current, container); if (hydrate && tag !== LegacyRoot) { const doc = container.nodeType === DOCUMENT_NODE ? container : container.ownerDocument; eagerlyTrapReplayableEvents(container, doc); } return root; }
createContainer
位于:react-reconciler/src/ReactFiberReconciler.old.js
作用:返回createFiberRoot
export function createContainer( containerInfo: Container, tag: RootTag, hydrate: boolean, hydrationCallbacks: null | SuspenseHydrationCallbacks,): OpaqueRoot { return createFiberRoot(containerInfo, tag, hydrate, hydrationCallbacks); }
createFiberRoot
位于:react-reconciler/src/react-reconciler/src/ReactFiberReconciler.old.js
作用: 新建FiberRoot对象并赋值给root,初始化Fiber(通常叫做RootFiber)通过root.current = uninitializedFiber和uninitializedFiber.stateNode = root将两者联系起来。
执行initializeUpdateQueue(uninitializedFiber)
创建一个更新队列,挂载fiber.updateQueue下面
最后将root返回
export function createFiberRoot( containerInfo: any, tag: RootTag, hydrate: boolean, hydrationCallbacks: null | SuspenseHydrationCallbacks,): FiberRoot { // 新建fiberRoot对象 const root: FiberRoot = (new FiberRootNode(containerInfo, tag, hydrate): any); if (enableSuspenseCallback) { root.hydrationCallbacks = hydrationCallbacks; } // Cyclic construction. This cheats the type system right now because // stateNode is any. //初始化RootFiber const uninitializedFiber = createHostRootFiber(tag); root.current = uninitializedFiber; // RootFiber的stateNode指向FiberRoot uninitializedFiber.stateNode = root; initializeUpdateQueue(uninitializedFiber); return root; }
FiberRoot RootFiber 和 updateQueue
ReactDOM.render主要创建了三个对象FiberRooat、RootFiber和Updatequeue下面我们这对这三个对象进行分析
FiberRoot
FiberRoot是FiberRootNode(containerInfo, tag, hydrate)的实例
位于:react-reconciler/src/ReactFiberRoot/FiberRootNode
作用:
- 整个应用的起点
- 包含应用挂载的目标节点
- 记录整个应用更新过程的各种信息
function FiberRootNode(containerInfo, tag, hydrate) { // 标记不同的组件类型 this.tag = tag; // 当前应用对应的Fiber对象,是Root Fiber // current:Fiber对象 对应的是 root 节点,即整个应用根对象 this.current = null; // root节点,render方法接收的第二个参数 this.containerInfo = containerInfo; // 只有在持久更新中会用到,也就是不支持增量更新的平台,react-dom不会用到 this.pendingChildren = null; this.pingCache = null; //任务有三种,优先级有高低: //(1)没有提交的任务 //(2)没有提交的被挂起的任务 //(3)没有提交的可能被挂起的任务 //当前更新对应的过期时间 this.finishedExpirationTime = NoWork; //已经完成任务的FiberRoot对象,如果你只有一个Root,那么该对象就是这个Root对应的Fiber或null //在commit(提交)阶段只会处理该值对应的任务 this.finishedWork = null; // 在任务被挂起的时候通过setTimeout设置的返回内容,用来下一次如果有新的任务挂起时清理还没触发的timeout(例如suspense返回的promise) this.timeoutHandle = noTimeout; // 顶层context对象,只有主动调用renderSubTreeIntoContainer时才会被调用 this.context = null; this.pendingContext = null; // 第一次渲染是否需要调和 this.hydrate = hydrate; // Node returned by Scheduler.scheduleCallback this.callbackNode = null; this.callbackPriority = NoPriority; //存在root中,最旧的挂起时间 //不确定是否挂起的状态(所有任务一开始均是该状态) this.firstPendingTime = NoWork; this.firstSuspendedTime = NoWork; this.lastSuspendedTime = NoWork; this.nextKnownPendingLevel = NoWork; //存在root中,最新的挂起时间 //不确定是否挂起的状态(所有任务一开始均是该状态) this.lastPingedTime = NoWork; this.lastExpiredTime = NoWork; this.mutableSourcePendingUpdateTime = NoWork; if (enableSchedulerTracing) { this.interactionThreadID = unstable_getThreadID(); this.memoizedInteractions = new Set(); this.pendingInteractionMap = new Map(); } if (enableSuspenseCallback) { this.hydrationCallbacks = null; } }
RootFiber
RootFiber初始化于const uninitializedFiber = createHostRootFiber(tag) 通过 createFiber 返回 FiberNode的实例 作用:
- 每个ReactElement对应一个Fiber对象
- 记录节点的各种状态(方便了hooks,因为记录state和props都是在Fiber只是完成后再挂载到this的例如:pendingProps pendingState memoizedProps memoizedState)
- 串联整个应用形成树结构
// 位于 react-reconciler/src/ReactFiber.js export function createHostRootFiber(tag: RootTag): Fiber { let mode; if (tag === ConcurrentRoot) { mode = ConcurrentMode | BlockingMode | StrictMode; } else if (tag === BlockingRoot) { mode = BlockingMode | StrictMode; } else { mode = NoMode; } return createFiber(HostRoot, null, null, mode); } const createFiber = function( tag: WorkTag, pendingProps: mixed, key: null | string, mode: TypeOfMode, ): Fiber { return new FiberNode(tag, pendingProps, key, mode); }; // FiberNode结构 function FiberNode( tag: WorkTag, pendingProps: mixed, key: null | string, mode: TypeOfMode, ) { // Instance // 标记不同的组件类型 this.tag = tag; // ReactElement里面的key this.key = key; // ReactElement.type,也就是我们调用`createElement`的第一个参数 this.elementType = null; // 异步组件lazy component resolved之后返回的内容,一般是`function`或者`class`组件 this.type = null; // 对应节点的实例,比如类组件就是class的实例,如果是dom组件就是dom实例,如果是function component就没有实例这里为空 this.stateNode = null; // Fiber Fiber是个链表通过child和Sibling连接,遍历的时候先遍历child如果没有子元素了则访问return回到上级查询是否有sibling // 指向他在Fiber节点树中的‘parent',用来在处理完这个节点之后向上返回 this.return = null; // 指向第一个子节点 this.child = null; // 指向自己的兄弟节点,兄弟节点的return指向同一个副节点 this.sibling = null; this.index = 0; this.ref = null; // 新的变动带来的新的props this.pendingProps = pendingProps; // 上次渲染完成后的props this.memoizedProps = null; // 该Fiber对应的组件产生的update会存放在这个队列(比如setState和forceUpdate创建的更新) this.updateQueue = null; // 上一次的state this.memoizedState = null; this.dependencies = null; // this.mode = mode; // Effects // 用来记录副作用 this.effectTag = NoEffect; // 单链表用来快速查找下一个side effect this.nextEffect = null; // 子树中第一个side effect this.firstEffect = null; // 子树中最后一个side effect this.lastEffect = null; // 代表任务在未来的哪个时候应该被完成 就是过期时间 // 不包括他的子树产生的任务 this.expirationTime = NoWork; // 快速确定子树中是否有不再等待的变化 this.childExpirationTime = NoWork; // Fiber树更新过程中,每个FIber都会有一个跟其对应的Fiber // 我们称他为`current <==> workInProgress` // 渲染完成后他们会交换位置 this.alternate = null; // 调试相关的去掉了 }
updateQueue
initializeUpdateQueue(uninitializedFiber);
位于:react-reconciler/src/ReactUpdateQueue.js
作用:单向链表,用来存放update,next来串联update
关于Update和UpdateQueue涉及到的东西比较多打算单独一章来讲解
export function initializeUpdateQueue<State>(fiber: Fiber): void { const queue: UpdateQueue<State> = { // 每次操作完更新阿之后的state baseState: fiber.memoizedState, // 队列中的第一个`Update` firstBaseUpdate: null, // 队列中的最后一个`Update` lastBaseUpdate: null, shared: { pending: null, }, effects: null, }; fiber.updateQueue = queue; }
流程图
最后是画的大致流程图
加载全部内容