React源码解析之ReactDOM.render()
一、React更新的方式有三种:
(1)ReactDOM.render() || hydrate(ReactDOMServer渲染)
(2)setState
(3)forceUpdate
接下来,我们就来看下ReactDOM.render()
源码
二、ReactDOM.render(element, container[, callback])
作用:
在提供的container
里渲染一个React
元素,并返回对该组件的引用
常见的用法是这个:
ReactDOM.render(<App />, document.getElementById('root'));
官网网址:
https://zh-hans.reactjs.org/docs/react-dom.html#render
源码:
const ReactDOM: Object = { //服务端使用hydrate方法渲染节点 hydrate(element: React$Node, container: DOMContainer, callback: ?Function) { invariant( isValidContainer(container), 'Target container is not a DOM element.', ); // TODO: throw or warn if we couldn't hydrate? return legacyRenderSubtreeIntoContainer( null, element, container, //true是让服务端尽可能复用节点,提高性能 true, callback, ); }, render( //元素 element: React$Element<any>, //容器 container: DOMContainer, //应用渲染结束后,调用的函数 callback: ?Function, ) { //错误抓取 invariant( isValidContainer(container), 'Target container is not a DOM element.', ); //render方法本质是返回了函数legacyRenderSubtreeIntoContainer return legacyRenderSubtreeIntoContainer( null, element, container, //render不会复用节点,因为是前端渲染 false, callback, ); }, }
解析:
(1)render()
方法本质是返回了函数legacyRenderSubtreeIntoContainer()
(2)hydrate()
和render()
的唯一区别是传入legacyRenderSubtreeIntoContainer()
的第四个参数不一样:hydrate()
为true
,表示在服务端尽可能复用节点,提高性能;render()
为false
,表示在浏览器端不会去复用节点,而是全部替换掉。
三、legacyRenderSubtreeIntoContainer()
作用:
初始化container
源码:
// null, element, container, false, callback, function legacyRenderSubtreeIntoContainer( parentComponent: ?React$Component<any, any>, children: ReactNodeList, container: DOMContainer, forceHydrate: boolean, callback: ?Function, ) { // TODO: Without `any` type, Flow says "Property cannot be accessed on any // member of intersection type." Whyyyyyy. //render中一般渲染的是DOM标签,所以不会有_reactRootContainer存在, // 所以第一次渲染,root是不存在的 let root: _ReactSyncRoot = (container._reactRootContainer: any); let fiberRoot; if (!root) { // Initial mount //创建一个ReactRooter root = container._reactRootContainer = legacyCreateRootFromDOMContainer( container, forceHydrate, ); fiberRoot = root._internalRoot; //判断是否有callback if (typeof callback === 'function') { const originalCallback = callback; callback = function() { //根据fiberRoot获取公共Root实例 //就是fiberRoot.current.child.stateNode const instance = getPublicRootInstance(fiberRoot); //通过该实例instance 去调用originalCallback方法 originalCallback.call(instance); }; } // Initial mount should not be batched. //初始化安装不应该批量更新 unbatchedUpdates(() => { //element,fiberRoot,null,callback 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); }
解析:
(1)由于是第一次渲染更新,所以root
是null
,只需看!root
的情况
(2)legacyCreateRootFromDOMContainer(container,false,)
的作用是创建ReactRooter
,稍后会讲解
(3)unbatchedUpdates(fn)的简化源码如下:
unbatchedUpdates(fn){ return fn() }
(4)updateContainer()
的作用是更新container
,稍后讲解
四、legacyCreateRootFromDOMContainer(container,forceHydrate,)
作用:
创建一个ReactRooter
源码:
//创建ReactRooter function legacyCreateRootFromDOMContainer( container: DOMContainer, forceHydrate: boolean, ): _ReactSyncRoot { //是否是服务端渲染 const shouldHydrate = //render的forceHydrate是false,所以会调用shouldHydrateDueToLegacyHeuristic方法来判断是否是服务端渲染 forceHydrate || shouldHydrateDueToLegacyHeuristic(container); // First clear any existing content. //如果不是服务端渲染的话 if (!shouldHydrate) { let warned = false; let rootSibling; //循环删除container的子节点 //为什么要删除?因为React认为这些节点是不需要复用的 while ((rootSibling = container.lastChild)) { container.removeChild(rootSibling); } } // Legacy roots are not batched. //container是空的container,0,false //ReactRoot是同步的 //sync 同步 //async 异步 return new ReactSyncRoot(container, LegacyRoot, shouldHydrate); }
解析:
(1)render()
的forceHydrate
是false
,所以看shouldHydrateDueToLegacyHeuristic(container)
是否返回false
① shouldHydrateDueToLegacyHeuristic()
作用:
判断是否是服务端渲染
源码:
//判断是否是服务端渲染 function shouldHydrateDueToLegacyHeuristic(container) { //获取container的第一个节点(根节点) //也就是 id='root' 的节点 const rootElement = getReactRootElementInContainer(container); return !!( rootElement && rootElement.nodeType === ELEMENT_NODE && //判断是否是服务端渲染 rootElement.hasAttribute(ROOT_ATTRIBUTE_NAME) ); }
② getReactRootElementInContainer()
作用:
获取container
中的第一个节点(或文档节点)
源码:
//获取Container里的RectRoot元素 //返回document节点或第一个子节点 function getReactRootElementInContainer(container: any) { if (!container) { return null; } //DOCUMENT_NODE 即 window.document if (container.nodeType === DOCUMENT_NODE) { return container.documentElement; } else { return container.firstChild; } }
也就是说,判断是否是服务端渲染的标志是:
在获取container
中的第一个节点(或文档节点)后,看该节点是否有属性ROOT_ATTRIBUTE_NAME
ROOT_ATTRIBUTE_NAME
是什么呢?
//服务端渲染的话,会在React App的第一个元素上添加该属性 //以标识是服务端渲染的 export const ROOT_ATTRIBUTE_NAME = 'data-reactroot';
即data-reactroot
(2)由(1)可知,render()
的container
的首节点是没有data-reactroot
属性的,所以会进行while
循环,依次删除container
的子节点,删除完毕后,new 一个ReactSyncRoot()
的实例
(3)ReactSyncRoot()
作用:
创建ReactRoot
实例
源码:
// container,0,false function ReactSyncRoot( container: DOMContainer, tag: RootTag, hydrate: boolean, ) { // Tag is either LegacyRoot or Concurrent Root const root = createContainer(container, tag, hydrate); this._internalRoot = root; }
把创建的root
作为legacyCreateRootFromDOMContainer()
的__internalRoot
属性
① createContainer
作用:
创建React容器
源码:
//创建React容器 export function createContainer( containerInfo: Container, tag: RootTag, hydrate: boolean, ): OpaqueRoot { //创建FiberRoot return createFiberRoot(containerInfo, tag, hydrate); }
也就是说legacyCreateRootFromDOMContainer()
的本质是创建了FilberRoot
五、updateContainer()
作用:
创建更新container
源码:
//更新Container export function updateContainer( element: ReactNodeList, container: OpaqueRoot, parentComponent: ?React$Component<any, any>, callback: ?Function, ): ExpirationTime { const current = container.current; //1073741823 const currentTime = requestCurrentTime(); const suspenseConfig = requestCurrentSuspenseConfig(); //计算过期时间,这是React优先级更新非常重要的点 const expirationTime = computeExpirationForFiber( currentTime, current, suspenseConfig, ); return updateContainerAtExpirationTime( element, container, parentComponent, expirationTime, suspenseConfig, callback, ); }
解析:
(1)requestCurrentTime()
作用:
计算新开始的时间
源码不用看,只需要知道该时间,是以V8
引擎上最大31
位整数1073741823
为根据的:
// Max 31 bit integer. The max integer size in V8 for 32-bit systems. // Math.pow(2, 30) - 1 // 0b111111111111111111111111111111 //整型最大数值,是V8中针对32位系统所设置的最大值 export default 1073741823;
(2)requestCurrentSuspenseConfig()
和computeExpirationForFiber()
以后会讲解
(3)updateContainerAtExpirationTime()
作用:
每到过期时间,就更新container
,过期时间单位为 10ms
源码:
//在过期时间内,更新container export function updateContainerAtExpirationTime( element: ReactNodeList, container: OpaqueRoot, parentComponent: ?React$Component<any, any>, expirationTime: ExpirationTime, suspenseConfig: null | SuspenseConfig, callback: ?Function, ) { // TODO: If this is a nested container, this won't be the root. const current = container.current; //由于parentComponent为null,所以返回空对象{} const context = getContextForSubtree(parentComponent); if (container.context === null) { container.context = context; } else { container.pendingContext = context; } //计划更新Root return scheduleRootUpdate( current, element, expirationTime, suspenseConfig, callback, ); }
解析:
① scheduleRootUpdate()
作用:
计划更新Root
源码:
//计划更新Root function scheduleRootUpdate( current: Fiber, element: ReactNodeList, expirationTime: ExpirationTime, suspenseConfig: null | SuspenseConfig, callback: ?Function, ) { //创建更新的时间节点 const update = createUpdate(expirationTime, suspenseConfig); // Caution: React DevTools currently depends on this property // being called "element". update.payload = {element}; callback = callback === undefined ? null : callback; if (callback !== null) { warningWithoutStack( typeof callback === 'function', 'render(...): Expected the last optional `callback` argument to be a ' + 'function. Instead received: %s.', callback, ); update.callback = callback; } if (revertPassiveEffectsChange) { flushPassiveEffects(); } //一整个React应用中,会有多次更新,而这多次更新均在更新队列中 enqueueUpdate(current, update); //进行任务调度 //当React进行Update后,就要进行调度 //即 根据任务的优先级去调度任务 //先执行优先级高的任务, scheduleWork(current, expirationTime); return expirationTime; }
解析:
任务调度是React
中最重要、复杂的内容,之后会慢慢来解析。
这里可以看到,React
将初始化的Update
放入了更新队列中,并进行任务调度,最终返回了一个expirationTime
也就是说,updateContainer()
本质是返回了expirationTime
六、getPublicRootInstance()
作用:
获取root实例
源码:
//获取root实例 export function getPublicRootInstance( container: OpaqueRoot, ): React$Component<any, any> | PublicInstance | null { //看到container.current,我就想到了ref(xxx.current) //获取当前节点 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; } }
解析:
由于是 React 初始化,所以container.current
是没有子节点的,所以该方法返回 null
七、ReactDOM.render()流程图
总结:
ReactDOM.render() 的更新步骤
(1)创建 ReactRoot,ReactRoot 是创建整个React应用的根对象
(2)创建 FiberRoot 和 RootFiber
(3)创建更新 (创建更新后,就会进入调度阶段,调度阶段由调度器进行管理)
(完)