React.createClass

从源码可以看到,React.createClass = ReactCompositeComponent.createClass, 因此真正的实现其实是在ReactCompositeComponent中。

简单来说,createClass时就做了两件事情。第一件事情,就是继承了相关基类的方法;第二件事情,就是做了一个wrapper,方便后续的调用。

继承细节

继承相关的代码如下:


createClass: function(spec) {
    var Constructor = function() {};
    Constructor.prototype = new ReactCompositeComponentBase();
    Constructor.prototype.constructor = Constructor;
    mixSpecIntoComponent(Constructor, spec);
    invariant(
      Constructor.prototype.render,
      'createClass(...): Class specification must implement a `render` method.'
    );

    // ... 暂时省略后续代码,先讲前半部分
  }

这段代码,首先定一个空函数Constructor,然后更新了它的prototype,这是一个非常经典的继承技巧。通过这样的方式,既使得Constructor拥有了ReactCompositeComponentBase这个类的方法,又避免了Constructor在修改prototype时,将ReactCompositeComponentBase的内容也修改掉了。

ReactCompositeComponentBase其实也是一个空函数,不过在它定义后,将ReactComponent,ReactOwner,ReactPropTransferer以及ReactCompositeComponentMixin这四个对象的Mixin都“继承”了下来。继承的是通过mixInto来实现的。

mixInto是一个工具函数,定义在util/mixInto.js中。它实现的功能很简单,就是将第二个传参中的方法添加到第一个参数对象的prototype中。只有第二个参数自身包含的属性/方法才会被添加给第一个参数的原型。同时,第一个参数的prototype若已经包含了相同属性/方法,则会被直接覆盖!

而Mixin机制,是为了实现“多继承”的手段。有一些分别属于A、B、C、D不同类的一些方法,可能都需要被E、F、G、H所拥有。为了简化继承关系,直接将这些被共享的方法添加到Mixin中,通过mixInto就实现了方法的共享/添加。mixSpecIntoComponent也做了类似的事情,它是将createClass时的属性信息spec添加到新的类中。

首先,也只会添加spec的ownProperty到新的类,而不会将spec原型链上的属性拿来处理。

同时,如果spec的某个prop已经在ReactCompositeComponentMixin定义了这个,那么这个属性必须是SpecPolicy.OVERRIDE_BASE,才能进行覆盖,否则报错。而这个SpecPolicy.OVERRIDE_BASE是啥呢?

SpecPolicy

它是一组枚举值SpecPolicy的一个成员。SpecPolicy定义了三种Policy,分别是DEFINE_ONCE,DEFINE_MANY和OVERRIDE_BASE,源码中也对这三种值做出了解释。

DEFINE_ONCE:只允许在spec或者mixin中定义一次,比如props和getInitialState、render方法、shouldComponentUpdate方法

DEFINE_MANY:可以既出现在spec中,也出现在mixin中,并且这些定义的方法都会被链式调用,但这些方法必须返回空。例如componentWillMount、componentDidMount、componentWillReceiveProps、componentWillUpdate、componentDidUpdate、componentWillUnmount

OVERRIDE_BASE: 将覆盖ReactCompositeComponent中的属性,比如updateComponent方法。

spec的值,在添加到prototype上之前,都要检查其Policy是否满足要求。而针对spec中不同的key,添加时也会有不同处理,按照如下的优先级顺序处理

  • 当key是displayName时,则会直接被添加到空的Construct中,作为同名属性
  • 当key为mixins,则会通过mixSpecIntoComponent给新的Constructor添加Mixin
  • 当key为props时,会给新的类添加propDeclarations,而非props。propDeclarations拿来对属性进行类型检测,但与typescript的编译阶段检测不同,这里是运行阶段进行测试。具体的调用时机,则是在mountComponent时,用propDeclarations对传递的props进行检测。
  • 当key对应的property存在,且有__reactAutoBind时,则将它添加到Constructor.prototype的__reactAutoBindMap上
  • 若key在Constructor.prototype上已经定义,则创建链式调用。==不过有个注意点,被串联调用的函数,参数不能超过5个。==createChaindeFunction的源代码非常简单,就是将两个需要串联调用的函数wrapper到同一个函数中,依次调用。createChainedFunction返回的就是这个wrapper函数。当要实现超过2个函数的链式调用时,只要先将其中两个先createChainedFunction,再将新生成的wrapper和另一个函数调用createChainedFunction。
  • 以上情况都不满足时,直接添加为Constructor.prototype的属性

构造函数Wrapper

前面那么多的内容,都是在给新的Constructor添加各种属性、方法。在createClass时,还会给新的构造函数添加一个wrapper,以便接受props和children等参数。wrapper的代码如下:


    var ConvenienceConstructor = function(props, children) {
      var instance = new Constructor();
      instance.construct.apply(instance, arguments);
      return instance;
    };
    ConvenienceConstructor.componentConstructor = Constructor;
    ConvenienceConstructor.originalSpec = spec;

非常好理解的代码,在wrapper函数中,首先创建一个实例,然后通过construct函数进行初始化,随后返回这个实例。为什么要这么做?

**能不能直接将Constructor声明成function Constructor(props, children)?**其实,可以通过阅读代码发现,真正的构造过程,其实都是在construct函数中实现的。为了保证每次实例化Constructor对象时,construct函数都会被调用,才有了这个wrapper。construct函数是从ReactComponent中获得的,这个函数主要完成了对props,_lifeCycleState和children等属性的初始化。而且construct函数有可能还会被动态更新。
在ReactComponent.Mixin中,就定义了一个construct函数,如果新传递的spec中同样定义了construct函数,为了保证这些construct函数都会被正常调用,会通过createChainedFunction来实现。

Show Comments

Get the latest posts delivered right to your inbox.