React@15.6.2源码解析---从 ReactDOM.render 到页面渲染(2)instantiateReactComponent

366 阅读6分钟

instantiateReactComponent 方法是React中的一个很重要的方法,主要作用是根据给定的node(ReactElement类型)对象,实例化出一个将被挂载的实例。实例化出的实例大体有三种,ReactEmptyComponent、ReactCompositeComponent、ReactHostComponent,其中ReactCompositeComponent是重点,内部包装了很多方法,而ReactHostComponent主要用来实例化文本组件实例。

概述 Given a ReactNode, create an instance that will actually be mounted. 给一个ReactNode 指的是 ReactElement 对象,根据该对象创建一个实际要挂载的实例

大致流程 判断 node 的类型以及 node.type 的类型,根据不同的类型创建不同的实例

node 为 null | false

if (node === null || node === false) {
    instance = ReactEmptyComponent.create(instantiateReactComponent);
  } else if (typeof node === 'object') {
    var element = node;
    var type = element.type; // function
    if (typeof type !== 'function' && typeof type !== 'string') {
      var info = '';
      if (process.env.NODE_ENV !== 'production') {
        /**/
      }
      info += getDeclarationErrorAddendum(element._owner); // ''
      !false ? /**/
    }

    // Special case string values
    if (typeof element.type === 'string') {
      instance = ReactHostComponent.createInternalComponent(element);
    } else if (isInternalComponentType(element.type)) {
      if (!instance.getHostNode) {
        instance.getHostNode = instance.getNativeNode;
      }
    } else {
      instance = new ReactCompositeComponentWrapper(element);
    }
  } else if (typeof node === 'string' || typeof node === 'number') {
    instance = ReactHostComponent.createInstanceForText(node);
  } else {
    !false ? /**/
  }

node 的类型是null或者为false,创建一个ReactEmptyComponent实例。

ReactEmptyComponent

// ReactEmptyComponent.js
var emptyComponentFactory;

var ReactEmptyComponentInjection = {
  injectEmptyComponentFactory: function (factory) {
    emptyComponentFactory = factory;
  }
};

var ReactEmptyComponent = {
  create: function (instantiate) {
    return emptyComponentFactory(instantiate);
  }
};

ReactEmptyComponent.injection = ReactEmptyComponentInjection;

module.exports = ReactEmptyComponent;

这边可以看出ReactEmptyComponent.create方法就是调用emptyComponentFactory()方法,而emptyComponentFactory是外部闭包的一个对象,是在全局依赖注入是调用ReactEmptyComponent.injection时进行注入的。注入的地方在ReactDefaultInjection.js文件,这也是上一篇博客的开头所阐述的全局依赖注入,作用就在于给一些函数外部闭包的变量对象某个属性进行赋值,这样做方便管理,神乎其技的封装。我觉得这种优秀的封装也是我们该学习的一部分。有助于我们写出更加完善的代码。

ReactInjection.EmptyComponent.injectEmptyComponentFactory(function (instantiate) {
    return new ReactDOMEmptyComponent(instantiate);
  });

那么这边给emptyComponentFactory赋值的是一个函数,这个函数返回一个new ReactDOMEmptyComponent(instantiate);ReactDOMEmptyComponent实例。

ReactDOMEmptyComponent实例

var ReactDOMEmptyComponent = function (instantiate) {
  // ReactCompositeComponent uses this:
  this._currentElement = null;
  // ReactDOMComponentTree uses these:
  this._hostNode = null;
  this._hostParent = null;
  this._hostContainerInfo = null;
  this._domID = 0;
};
_assign(ReactDOMEmptyComponent.prototype, {
  mountComponent: function (transaction, hostParent, hostContainerInfo, context) {
    /**/
    }
  },
  receiveComponent: function () {},
  getHostNode: function () {
    return ReactDOMComponentTree.getNodeFromInstance(this);
  },
  unmountComponent: function () {
    ReactDOMComponentTree.uncacheNode(this);
  }
});

这边我们大致了解一下有那些属性等用到时在做阐述。那么由此可以看出,ReactEmptyComponent创建的实例实质上就是ReactDOMEmptyComponent

node 为 object##

当 node 的类型为一个object时,这边大多数情况指的是我们的node是一个ReactElement。接下来会对node.type做判断。

node.type !== function || node.type !== string

type 既不是 function 也不是一个 string 那么会报一个警告,讲道理要么是一个构造函数例如我们的App,那么是一个字符串例如一个div标签,下面给一个例子

image_1dlpb73ib1li31kp01u8v1el10g9.png-48.6kB

node一层层剥开,看他的child,剧透一下,后面一步步的忘深处创建实例,那么肯定会遇到一个type: 'div'的ReactElement,这个时候 type 就是string了

node.type === string

那么当node.type是一个字符串的时候,会创建一个ReactHostComponet实例,这边和文本节点有所区别,这边调用的是ReactHostComponet.createInternalComponent方法,文本节点的话会调用ReactHostComponet.createInstanceForText下方会遇到。

// ReactHostComponent.js
function createInternalComponent(element) {
  !genericComponentClass /**/
  return new genericComponentClass(element);
}

这个函数首先会判断genericComponentClass是否存在,而这个变量是函数外部闭包的一个变量,和之前一样是在全局依赖注入的时候完成了赋值。

var ReactHostComponentInjection = {
  // This accepts a class that receives the tag string. This is a catch all
  // that can render any kind of tag.
  injectGenericComponentClass: function (componentClass) {
    genericComponentClass = componentClass;
  },
  // This accepts a text component class that takes the text string to be
  // rendered as props.
  injectTextComponentClass: function (componentClass) {
    textComponentClass = componentClass;
  }
};

那么回到ReactDefaultInjection继续查找这边注入的是什么

ReactInjection.HostComponent.injectGenericComponentClass(ReactDOMComponent);

那么此时genericComponentClass就是这边传入的ReactDOMComponent,那么就是返回一个ReactDOMComponent实例

isInternalComponentType(element.type)

检测 node.type 是不是内部组件类型

function isInternalComponentType(type) {
  return typeof type === 'function' && typeof type.prototype !== 'undefined' && typeof type.prototype.mountComponent === 'function' && typeof type.prototype.receiveComponent === 'function';
}

如果是内部的类型的化,那么就直接实例化传入的node,具体例子我还没有遇到过,就不做过多理解了,有例子的同学欢迎指教。

node.type 不是上述类型

其实这边的意思就是 node.type 是一个function,因为 node.type 只能是 function 或者 string。只不过逃过了上述isInternalComponentType函数的检测。 那么这边是创建的一个ReactCompositeComponentWrapper实例,这个构造函数是React的一大重点,根据我的四级英语翻译为 ' React复合类型 ' 他的本质是ReactCompositeComponet内部实现了React的组件生命周期,挂载卸载等操作。具体的后面会有很大一部分讲解他。这也是代码量可以说最大的几个文件之一了。

// 这边是定义了一下这个构造函数,在该文件的最下面,对该构造函数的原型链添加了一系列的方法
var ReactCompositeComponentWrapper = function (element) {
  this.construct(element);
  // console.log(this);
};

_assign(ReactCompositeComponentWrapper.prototype, ReactCompositeComponent, {
  _instantiateReactComponent: instantiateReactComponent
});

这边可以看出ReactCompositeComponentWrapper的主要操作都在ReactCompositeComponent文件里,而这边覆盖了一个_instantiateReactComponent属性,值为当前讲解的这个创建实例的函数instantiateReactComponent

node 为 string | number

当 node 为 string 或者 number 时,表明这是一个文本组件,对调用ReactHostComponent.createInstanceForText,那么加上当 node.type 为 string 时的讲解,可以看出ReactHostComponent组件描述的都是React中的文本组件。而ReactHostComponent.createInstanceForText方法引用的是外部闭包的textComponentClass,一样也是在全局依赖注入的时候赋值的。

// ReactHostComponent.js
/**
 * @param {ReactText} text
 * @return {ReactComponent}
 */
function createInstanceForText(text) {
  return new textComponentClass(text);
}

// ReactDefaultInjection.js
ReactInjection.HostComponent.injectTextComponentClass(ReactDOMTextComponent);

那么本质就是创建一个 ReactDOMTextComponent 实例。

node 不是上述类型

这边不是上述类型,那么就报错了。

新增 _mountIndex _mountIage

在得到实例之后,会给instance添加两个额外的属性。根据官方给的英文注释,大致可以猜出这两个字段是用于 DOM 和 Diff 算法上的。React的Diff算法我也会写博客讲解,只不过得等这个渲染的一系列写完。React万岁!!!

 // These two fields are used by the DOM and ART diffing algorithms
  // respectively. Instead of using expandos on components, we should be
  // storing the state needed by the diffing algorithms elsewhere.
  // 这两个字段是 DOM 和 ART diff算法所需要的字段。我们应该在其他地方存储diff算法所需要的state,
  // 而不是在组件上扩展他
  // 这两个字段分别用于DOM和ART diffing算法。我们不应该在组件上使用expandos,而是应该存储其他不同算法所需的状态。
  instance._mountIndex = 0;
  instance._mountIage = null;

总结

instantiateReactComponent 函数总体来说还是很简单的,只是根据 node 的类型 和 node.type 的类型创建一个实例,这边的代码还是可以理解的,不了解实例内部的那些方法的话难度还是可以接受的,难的地方还在后面,那么对该函数做一个流程图如下

image_1dlpdfmtm1os311vit91ndjjm.png-88.8kB

本片留坑

各个实例的内部属性、方法

坑总会填的,不急不急

下一篇讲解 ReactUpdates.js 这也是一个重点,博客是按照整个渲染的流程,部分重点文件会单独拉出来一篇进行理解。