回顾

在上篇分析后,最终得到如下函数调用过程。

在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也是一个参与后面调度的关键。

03-05 20:14