在 ReactDOM.render源码解析-1中介绍了第一次render的基本过程的一部分,其中产生了ReactRoot和ReactWork两个类的实例。本文介绍下ReactRoot,ReactWork源码,只关注第一次调用render的过程。
文章中如有不当之处,欢迎交流指点。react版本16.8.2
。在源码添加的注释在github react-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也是一个参与后面调度的关键。