ReactRoot与ReactWork源码分析
在ReactDOM.render源码解析-1中介绍了第一次render的基本过程的一部分,其中产生了ReactRoot和ReactWork两个类的实例。本文介绍下ReactRoot,ReactWork源码,只关注第一次调用render的过程。
文章中如有不当之处,欢迎交流指点。react版本16.8.2
。在源码添加的注释在githubreact-source-learn。
回顾
在上篇分析后,最终得到如下函数调用过程。
在render方法中调用了legacyRenderSubtreeIntoContainer。
在legacyRenderSubtreeIntoContainer中调用legacyCreateRootFromDOMContainer获得了ReactRoot的实例root,然后调用unbatchedUpdates,其中的回调函数中调用了root.render方法。RootRoot是什么?他的render方法做了什么?
代码分析
通过对ReactRoot和ReactWork代码的简单分析,笔者做了如下类图,以帮助了解这两个类有哪些属性和方法。
有很多东西不是第一次调用render用到的,这里只关注第一次render所需要调用方法或使用的属性。
ReactRoot
这个类主要介绍其构造函数和render方法,构造函数是new时调用的,render方法是unbatchedUpdates的回调函数中使用的。代码如下:
// ReactRoot构造函数 // 构造函数主要是挂了一个_internalRoot在this上 function ReactRoot( container: DOMContainer, // dom节点 isConcurrent: boolean, // 第一次render为false hydrate: boolean, // 第一次render为false ) { // 这个createContainer是packages/ReactFiberReconciler中的方法, // 返回的是一个OpaqueRoot的东西 const root = createContainer(container, isConcurrent, hydrate); this._internalRoot = root; } // render实例方法 new 了ReactWork, 调用了then方法 // 调用了updateContainer方法 // 返回了ReactWork实例 ReactRoot.prototype.render = function( children: ReactNodeList, // element callback: ?() => mixed, // ReactDOM.render(element, container, callback); callback ): Work { const root = this._internalRoot; const work = new ReactWork(); callback = callback === undefined ? null : callback; if (callback !== null) { work.then(callback); } // updateContainer是packages/react-reconciler/ReactFiberReconciler.js中的 // 一个方法,后边再说 updateContainer(children, root, null, work._onCommit); return work; };
先看构造函数
第一次render时new ReactRoot位于legacyCreateRootFromDOMContainer中,其调用代码如下:return new ReactRoot(container, isConcurrent, shouldHydrate)
。
container是我们调用ReactDOM.render时的第二个参数,一个dom,但是里边的子节点已被处理了,isConcurrent这里是写死的false,shouldHydrate上文分析过,为false。
再看ReactRoot的构造函数,他调用了一个createContainer并将返回值挂到了_internalRoot属性。这个createContainer将在下一篇分析。
看下render方法
我们先看看第一次render是调用他的代码,root.render(children, callback);
,其中children是ReactDOM.render的第一个参数,是个ReactElement, callback是第三个参数,通常不传。
这个render方法主要做了如下事:
- new ReactWork -> work
- 调用work的then方法
- 调用updateContainer
- 返回work
这里值得注意的是ReactWork类,这个将在后文分析;还有updateContainer,这个将在后面的文章分析,这里搞清楚第一调用时的参数给的啥,即ReactElement,createContainer返回的root,null,ReactWork实例的_onCommit方法。
ReactWork
ReactWork的方法在第一次render时都有可能被调用到,如下代码为ReactWork类的定义:
// ReactWork的构造函数 function ReactWork() { this._callbacks = null; this._didCommit = false; // TODO: Avoid need to bind by replacing callbacks in the update queue with // list of Work objects. this._onCommit = this._onCommit.bind(this); } // then方法 ReactWork.prototype.then = function(onCommit: () => mixed): void { if (this._didCommit) { // 第一次render调用then时为false, 不走这里 onCommit(); return; } let callbacks = this._callbacks; if (callbacks === null) { // 第一次render是调用走这里 callbacks = this._callbacks = []; } callbacks.push(onCommit); }; // _onCommit方法 ReactWork.prototype._onCommit = function(): void { if (this._didCommit) { // 第一次render不走这里 return; } this._didCommit = true; // 这个callbacks是调用.then方法是传进去的函数 const callbacks = this._callbacks; if (callbacks === null) { return; } // TODO: Error handling. for (let i = 0; i < callbacks.length; i++) { const callback = callbacks[i]; invariant( typeof callback === 'function', 'Invalid argument passed as callback. Expected a function. Instead ' + 'received: %s', callback, ); callback(); } };
构造函数
构造函数不接受参数,做了一些初始化工作
then方法
then方法调用时是work.then(callback);
,callback是ReactDOM的第三个参数
then方法的作用就是维护一个_callbacks队列,每次都将传进去的函数入队
_onCommit方法
这个方法的调用代码updateContainer(children, root, null, work._onCommit)
,其实是updateContainer的最后一个参数。
在这个里边将_didCommit置为true,回顾上边的ReactRoot的render方法,意味着这个方法被调用后在调ReactRoot.render是会直接执行callback的而不是入队。
然后是将_callbacks中的方法都执行了一遍。
小结
从上文的分析来看,接下来的重点是分析updateContainer这个方法,ReactWork的then方法是将callback入队,_onCommit是执行_callbacks中的所有方法,而调用_onCommit的是在updateContainer中,updateContainer实在ReactRoot.render方法中调用的,因此updateContainer应该是一个非常重要的东西。另外,ReactRoo.render方法是在unbatchedUpdates的回调函数中调用的,unbatchedUpdates也是一个参与后面调度的关键。