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

资源搜索网站大全 https://www.renrenfan.com.cn 广州VI设计公司https://www.houdianzi.com

六、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)创建更新 (创建更新后,就会进入调度阶段,调度阶段由调度器进行管理)


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM