DanceOnBeat

Keep Keep Keep 不再当一个混子
  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

React源码学习——ReactClass

Posted on 2017-05-24 13:18  DanceOnBeat  阅读(2063)  评论(0编辑  收藏  举报

前言

之前一直在使用react做开发,但是对其内部的工作机制却一点儿都不了解,说白了就是一直在套api,毫无成就感。趁最近比较闲,对源码做了一番研究,并通过博客的方式做一些记录。

进入正题

通过编写自定义组件来实现代码复用是react一个很亮眼的创新点,我们知道react创建组件一般使用两种方式:

  1. 通过React.createClass API
  2. 运用es6语法 class xx extends React.Component 

虽然后者正在逐渐取代前者,但是去研究一下前者也是很有必要的。我们先来看一看 createClass 方法,然后再去分析一下es6的写法,通过对比你将发现 createClass 被取代也是有理可循的。我们在源码中找到createClass方法,如图,在贴出的代码中我将作者原来的注释都删除了,并在必要的地方加上了自己的理解。

 1 var ReactClass = {
 2 
 3   createClass: function (spec) {
 4 
 5     //warning:createClass API将会在16版本被移除,现在通常是通过es6的语法创建组件,后面会进行说明
 6     if ("development" !== 'production') {
 7       "development" !== 'production' ? warning(didWarnDeprecated, '%s: React.createClass is deprecated and will be removed in version 16. ' + 'Use plain JavaScript classes instead. If you\'re not yet ready to ' + 'migrate, create-react-class is available on npm as a ' + 'drop-in replacement.', spec && spec.displayName || 'A Component') : void 0;
 8       didWarnDeprecated = true;
 9     }
10 
11     //identity返回参数中的匿名函数
12     var Constructor = identity(function (props, context, updater) {
13 
14       //warning: 必须通过new方法创建Constructor实例来使用组件
15       if ("development" !== 'production') {
16         "development" !== 'production' ? warning(this instanceof Constructor, 'Something is calling a React component directly. Use a factory or ' + 'JSX instead. See: https://fb.me/react-legacyfactory') : void 0;
17       }
18 
19       // 处理自动绑定的方法
20       if (this.__reactAutoBindPairs.length) {
21         bindAutoBindMethods(this);
22       }
23 
24       this.props = props;
25       this.context = context;
26       this.refs = emptyObject;
27       this.updater = updater || ReactNoopUpdateQueue;
28       this.state = null;
29 
30       //调用 getInitialState 初始化state
31       var initialState = this.getInitialState ? this.getInitialState() : null;
32       if ("development" !== 'production') {
33         if (initialState === undefined && this.getInitialState._isMockFunction) {
34           initialState = null;
35         }
36       }
37       //当 initialState 返回的不是object或null类型抛出错误
38       !(typeof initialState === 'object' && !Array.isArray(initialState)) ? "development" !== 'production' ? invariant(false, '%s.getInitialState(): must return an object or null', Constructor.displayName || 'ReactCompositeComponent') : _prodInvariant('82', Constructor.displayName || 'ReactCompositeComponent') : void 0;
39 
40       this.state = initialState;
41     });
42     //使Constructor继承ReactClassComponent
43     Constructor.prototype = new ReactClassComponent();
44     Constructor.prototype.constructor = Constructor;
45     Constructor.prototype.__reactAutoBindPairs = [];
46     
47     //将通过 ReactClass.injection.injectMixin(mixin) 方法传入的mixin注入到Constructor中去
48     injectedMixins.forEach(mixSpecIntoComponent.bind(null, Constructor));
49 
50     // 将 createClass 参数中定义的属性或方法混入到 Constructor.prototype 或 Constructor 中,其中分了很多情况,后面详细说明
51     mixSpecIntoComponent(Constructor, spec);
52 
53     // 调用 getDefaultProps 初始化 defaultProps,挂载组件时会作为参数传给new Constructor(),注意 getDefaultProps 不在prototype上
54     if (Constructor.getDefaultProps) {
55       Constructor.defaultProps = Constructor.getDefaultProps();
56     }
57 
58     if ("development" !== 'production') {
59       // getDefaultProps 只能在createClass中使用,通过 isReactClassApproved 防止在外部调用
60       if (Constructor.getDefaultProps) {
61         Constructor.getDefaultProps.isReactClassApproved = {};
62       }
63       // 同上
64       if (Constructor.prototype.getInitialState) {
65         Constructor.prototype.getInitialState.isReactClassApproved = {};
66       }
67     }
68     // 必须定义render方法,否则抛出错误
69     !Constructor.prototype.render ? "development" !== 'production' ? invariant(false, 'createClass(...): Class specification must implement a `render` method.') : _prodInvariant('83') : void 0;
70     // 一些拼写错误的warning
71     if ("development" !== 'production') {
72       "development" !== 'production' ? warning(!Constructor.prototype.componentShouldUpdate, '%s has a method called ' + 'componentShouldUpdate(). Did you mean shouldComponentUpdate()? ' + 'The name is phrased as a question because the function is ' + 'expected to return a value.', spec.displayName || 'A component') : void 0;
73       "development" !== 'production' ? warning(!Constructor.prototype.componentWillRecieveProps, '%s has a method called ' + 'componentWillRecieveProps(). Did you mean componentWillReceiveProps()?', spec.displayName || 'A component') : void 0;
74     }
75 
76     // 这是一个优化,后面详细说明
77     for (var methodName in ReactClassInterface) {
78       if (!Constructor.prototype[methodName]) {
79         Constructor.prototype[methodName] = null;
80       }
81     }
82 
83     return Constructor;
84   },
85 
86   injection: {
87     injectMixin: function (mixin) {
88       injectedMixins.push(mixin);
89     }
90   }
91 
92 };

 

首先可以看到,createClass 实际上返回的是一个 Constructor 构造函数,该构造函数拥有5个实例属性,分别是:props,context,refs,updater和state。props 即为通常我们用于组件之间通信的 props,存储组件的一些相关属性。它实际上由3个部分组成:

调用 createElement 时传入的config,children 和 createClass 中调用 getDefaultProps 方法获得的 defaultProps。举个例子,如下列代码: 

var Root = React.createClass({
    getDefaultProps: function() {
        return {
            name: 'Ray'
        }
    },

    render: function() {
        let me = this;
        console.log(me.props);
        return (
            <div>
                <h1>Hello, {me.props.name}, {me.props.age} years old</h1>
                {this.props.children}
            </div>
        ) 
    }
})

var ele = React.createElement(Root, {age: '18'}, "i'm a child");

ReactDOM.render(
    ele,
    document.getElementById('dom')
);

打印出props,如下图:

可以看到 props 属性包含了在 createElement 中传入的config和children参数,还有在 getDefaultProps 中定义的默认值,这也就是为什么我们可以在组件用 {this.props.children} 这种方式获取到children。

关于 createElement 的具体实现会在之后相关文章中分析。

第二个实例属性 context 即保存组件挂载或更新时的上下文环境,react会为你处理好一切,这里就不展开了。

然后是refs,保存着对组件render方法内的dom节点的引用。如果是自定义的元素节点,即我们创建的组件,则会保存着一个我们在挂载组件时用到的 ReactCompositeComponent 的实例。如果是基本元素类型,如div,p等,则直接返回其真实dom。注意,该属性不能再render中使用,因为此时组件还未挂载。

updater属性保存着组件的更新队列,在组件更新时会用到。

state属性即保存组件的状态。

在定义了Constructor构造函数后(注意:此时还未执行该函数,仅仅是定义,生成实例是在挂载组件的时候进行的),见41,42行,运用了简单的原型链继承,是 Costructor 对象继承了 ReactClassComponent,我们来看看ReactClassComponent具体有哪些属性和方法。

1 var ReactClassComponent = function () {};
2 _assign(ReactClassComponent.prototype, ReactComponent.prototype, ReactClassMixin);

 

这段代码将 ReactComponent.prototype 和 ReactClassMixin 混入到ReactClassComponent.prototype上,再看看这两者代码。

function ReactComponent(props, context, updater) {
  this.props = props;
  this.context = context;
  this.refs = emptyObject;
  // We initialize the default updater but the real one gets injected by the
  // renderer.
  this.updater = updater || ReactNoopUpdateQueue;
}

ReactComponent.prototype.isReactComponent = {};

ReactComponent.prototype.setState = function (partialState, callback) {
  !(typeof partialState === 'object' || typeof partialState === 'function' || partialState == null) ? "development" !== 'production' ? invariant(false, 'setState(...): takes an object of state variables to update or a function which returns an object of state variables.') : _prodInvariant('85') : void 0;
  this.updater.enqueueSetState(this, partialState);
  if (callback) {
    this.updater.enqueueCallback(this, callback, 'setState');
  }
};

ReactComponent.prototype.forceUpdate = function (callback) {
  this.updater.enqueueForceUpdate(this);
  if (callback) {
    this.updater.enqueueCallback(this, callback, 'forceUpdate');
  }
};

var ReactClassMixin = {

  replaceState: function (newState, callback) {
    this.updater.enqueueReplaceState(this, newState);
    if (callback) {
      this.updater.enqueueCallback(this, callback, 'replaceState');
    }
  },

  isMounted: function () {
    return this.updater.isMounted(this);
  }
};

 

因此通过 Constructor.prototype 一共可以访问到5个方法,分别为:isReactComponent,setState,forceUpdate,replaceState和isMounted。

然后在48行有 injectedMixins.forEach(mixSpecIntoComponent.bind(null, Constructor)) 这样一段代码,在第86行可以看到,ReactClass 还有一个方法 injection,这个方法会把要混入的mixin添加到 injectedMixins 数组中。因此这里的 forEach 也就是要把 injectedMixins中所有的mixin混入到Constructor中去,这和 createClass的功能有些重复,并且 injection 方法并没有暴露到 React 中去,因此我也不太明白这个方法存在的意义。

接下来在51行执行 mixSpecIntoComponent(Constructor, spec) ,mixSpecIntoComponent 就是把我们传入的spec中的方法或属性混入到 Constructor,我们来看看这个方法。

 

 1 function mixSpecIntoComponent(Constructor, spec) {
 2   // 当传入的spec为null或者undefined时显示warning信息
 3   if (!spec) {
 4     if ("development" !== 'production') {
 5       var typeofSpec = typeof spec;
 6       var isMixinValid = typeofSpec === 'object' && spec !== null;
 7 
 8       "development" !== 'production' ? warning(isMixinValid, '%s: You\'re attempting to include a mixin that is either null ' + 'or not an object. Check the mixins included by the component, ' + 'as well as any mixins they include themselves. ' + 'Expected object but got %s.', Constructor.displayName || 'ReactClass', spec === null ? null : typeofSpec) : void 0;
 9     }
10 
11     return;
12   }
13 
14   // 当传入的spec为function类型或者是ReactElement对象,抛出异常
15   !(typeof spec !== 'function') ? "development" !== 'production' ? invariant(false, 'ReactClass: You\'re attempting to use a component class or function as a mixin. Instead, just use a regular object.') : _prodInvariant('75') : void 0;
16   !!ReactElement.isValidElement(spec) ? "development" !== 'production' ? invariant(false, 'ReactClass: You\'re attempting to use a component as a mixin. Instead, just use a regular object.') : _prodInvariant('76') : void 0;
17 
18   var proto = Constructor.prototype;
19   // __reactAutoBindPairs为一个数组的引用,见createClass方法,对autoBindPairs的修改会影响到__reactAutoBindPairs所引用的数组
20   var autoBindPairs = proto.__reactAutoBindPairs;
21 
22   // 先处理spec中的mixins字段,MIXINS_KEY即为'mixins'
23   if (spec.hasOwnProperty(MIXINS_KEY)) {
24     RESERVED_SPEC_KEYS.mixins(Constructor, spec.mixins);
25   }
26 
27   for (var name in spec) {
28     if (!spec.hasOwnProperty(name)) {
29       continue;
30     }
31 
32     if (name === MIXINS_KEY) {
33       // mixins字段前面已经处理过了
34       continue;
35     }
36 
37     var property = spec[name];
38     var isAlreadyDefined = proto.hasOwnProperty(name);
39     validateMethodOverride(isAlreadyDefined, name);
40 
41     //若是RESERVED_SPEC_KEYS中定义过的属性,则按照RESERVED_SPEC_KEYS中定义方法执行相应操作
42     if (RESERVED_SPEC_KEYS.hasOwnProperty(name)) {
43       RESERVED_SPEC_KEYS[name](Constructor, property);
44     } else {
45       // 满足以下条件的方法不应该被auto bind:
46       // 1. 是ReactClassInterface中定义的方法.
47       // 2. Constructor.prototype上已存在的方法.
48       var isReactClassMethod = ReactClassInterface.hasOwnProperty(name);
49       var isFunction = typeof property === 'function';
50       var shouldAutoBind = isFunction && !isReactClassMethod && !isAlreadyDefined && spec.autobind !== false;
51 
52       if (shouldAutoBind) {
53         // 将应该自动绑定的方法添加到autoBindPairs所引用的数组中
54         autoBindPairs.push(name, property);
55         proto[name] = property;
56       } else {
57         if (isAlreadyDefined) {
58           var specPolicy = ReactClassInterface[name];
59 
60           // 这部分操作已经在 validateMethodOverride 执行过了
61           !(isReactClassMethod && (specPolicy === 'DEFINE_MANY_MERGED' || specPolicy === 'DEFINE_MANY')) ? "development" !== 'production' ? invariant(false, 'ReactClass: Unexpected spec policy %s for key %s when mixing in component specs.', specPolicy, name) : _prodInvariant('77', specPolicy, name) : void 0;
62 
63           // 对于拥有 DEFINE_MANY_MERGED 或者 DEFINE_MANY标识的方法,当定义多次时,分别采取不同的处理
64           if (specPolicy === 'DEFINE_MANY_MERGED') {
65             proto[name] = createMergedResultFunction(proto[name], property);
66           } else if (specPolicy === 'DEFINE_MANY') {
67             proto[name] = createChainedFunction(proto[name], property);
68           }
69         } else {
70           proto[name] = property;
71           if ("development" !== 'production') {
72             // 给function类型的属性添加displayName属性,相当添加了个标识符,给使用分析工具提供了帮助
73             if (typeof property === 'function' && spec.displayName) {
74               proto[name].displayName = spec.displayName + '_' + name;
75             }
76           }
77         }
78       }
79     }
80   }
81 }

 

直接从20行开始说,这里创建了一个 autoBindPairs 变量,将 proto.__reactAutoBindPairs 赋给了它。而 proto.__reactAutoBindPairs 是一个对数组的引用,因此在该段代码54行执行 autoBindPairs.push(name, property) 时,实际上也影响到了 proto.__reactAutoBindPairs,因为他俩指向同一个数组,对其中任何一个的操作将会影响到另一个。

接下来在22行处理spec的注入时,首先会去处理mixins字段,无论它是否是第一个定义的,此时会去调用 RESERVED_SPEC_KEYS 中对应的方法。RESERVED_SPEC_KEYS 顾名思义,就是列出spec的一些保留的关键字,做特殊的处理。例如 getDefaultProps,propTypes等,这个对象中定义的属性是会被添加到Constructor上,而不是Constructor.prototype上,即作为Constructor的静态属性存在。这里在处理mixins时,实际上就是遍历mixins数组,然后对每一项执行 mixSpecIntoComponent 方法,这是一个递归的过程。这里就有一个问题,假如mixins和spec包含同名属性,该如何处理?不着急,继续往下看。

处理了mixins之后,就会去遍历spec对象,去处理其它的字段。在处理之前,首先会去调用 validateMethodOverride 方法,这个方法就是用来处理当混入了两个同名属性的情况,我们来看一下这段代码。

 1 function validateMethodOverride(isAlreadyDefined, name) {
 2   var specPolicy = ReactClassInterface.hasOwnProperty(name) ? ReactClassInterface[name] : null;
 3 
 4   // Disallow overriding of base class methods unless explicitly allowed.
 5   if (ReactClassMixin.hasOwnProperty(name)) {
 6     !(specPolicy === 'OVERRIDE_BASE') ? "development" !== 'production' ? invariant(false, 'ReactClassInterface: You are attempting to override `%s` from your class specification. Ensure that your method names do not overlap with React methods.', name) : _prodInvariant('73', name) : void 0;
 7   }
 8 
 9   // Disallow defining methods more than once unless explicitly allowed.
10   if (isAlreadyDefined) {
11     !(specPolicy === 'DEFINE_MANY' || specPolicy === 'DEFINE_MANY_MERGED') ? "development" !== 'production' ? invariant(false, 'ReactClassInterface: You are attempting to define `%s` on your component more than once. This conflict may be due to a mixin.', name) : _prodInvariant('74', name) : void 0;
12   }
13 }

 

ReactClassInterface可以理解为一个对象,对象中包含的key为属性或者方法名,value为对该属性或方法的描述,代码如下(为了简洁,删除了注释)。

 1 var ReactClassInterface = {
 2 
 3   mixins: 'DEFINE_MANY',
 4 
 5   statics: 'DEFINE_MANY',
 6 
 7   propTypes: 'DEFINE_MANY',
 8 
 9   contextTypes: 'DEFINE_MANY',
10 
11   childContextTypes: 'DEFINE_MANY',
12 
13   getDefaultProps: 'DEFINE_MANY_MERGED',
14 
15   getInitialState: 'DEFINE_MANY_MERGED',
16 
17   getChildContext: 'DEFINE_MANY_MERGED',
18 
19   render: 'DEFINE_ONCE',
20 
21   componentWillMount: 'DEFINE_MANY',
22 
23   componentDidMount: 'DEFINE_MANY',
24 
25   componentWillReceiveProps: 'DEFINE_MANY',
26 
27   shouldComponentUpdate: 'DEFINE_ONCE',
28 
29   componentWillUpdate: 'DEFINE_MANY',
30 
31   componentDidUpdate: 'DEFINE_MANY',
32 
33   componentWillUnmount: 'DEFINE_MANY',
34   
35   updateComponent: 'OVERRIDE_BASE'
36 
37 };

例如componentWillMount: 'DEFINE_MANY', 就是规定 componentWillMount 可以定义多次。

回到 validateMethodOverride 方法,首先 ReactClassMixin 对象前面说过,包含replaceState和isMounted方法,而描述为 OVERRIDE_BASE 的只有 updateComponent 方法,所以第一个if语句规定不能重写 ReactClassMixin 中的方法,否则抛出异常。isAlreadyDefined 为在Constructor.prototype中已存在的方法,如果一个方法已存在且不为 DEFINE_MANY 或者 DEFINE_MANY_MERGED,则抛出异常。如果是这两个其中一个的话,来看 mixSpecIntoComponent 的64-68行, 如果为 DEFINE_MANY_MERGED,会去调用 createMergedResultFunction 方法;如果为 DEFINE_MANY 会去调用 createChainedFunction 方法。我们来看看这两个函数的代码。

 1 function mergeIntoWithNoDuplicateKeys(one, two) {
 2   !(one && two && typeof one === 'object' && typeof two === 'object') ? "development" !== 'production' ? invariant(false, 'mergeIntoWithNoDuplicateKeys(): Cannot merge non-objects.') : _prodInvariant('80') : void 0;
 3 
 4   for (var key in two) {
 5     if (two.hasOwnProperty(key)) {
 6       !(one[key] === undefined) ? "development" !== 'production' ? invariant(false, 'mergeIntoWithNoDuplicateKeys(): Tried to merge two objects with the same key: `%s`. This conflict may be due to a mixin; in particular, this may be caused by two getInitialState() or getDefaultProps() methods returning objects with clashing keys.', key) : _prodInvariant('81', key) : void 0;
 7       one[key] = two[key];
 8     }
 9   }
10   return one;
11 }
12 
13 function createMergedResultFunction(one, two) {
14   return function mergedResult() {
15     var a = one.apply(this, arguments);
16     var b = two.apply(this, arguments);
17     if (a == null) {
18       return b;
19     } else if (b == null) {
20       return a;
21     }
22     var c = {};
23     mergeIntoWithNoDuplicateKeys(c, a);
24     mergeIntoWithNoDuplicateKeys(c, b);
25     return c;
26   };
27 }
28 
29 function createChainedFunction(one, two) {
30   return function chainedFunction() {
31     one.apply(this, arguments);
32     two.apply(this, arguments);
33   };
34 }

简单来说,这两个方法都返回了一个函数,返回的函数主要的作用是按顺序调用两个同名方法。不过在处理 DEFINE_MANY_MERGED 的时候,需要将执行后返回的对象合并,此时对象中若存在同名的属性,则会抛出异常。通过以上代码的第6行,我们可以看到这种情况通常是在mixins 和 spec中混入了 getInitialState 或者 getDefaultProps,并且返回的对象中包含同名属性造成的。因此可以得出一个结论,当我们在mixins或者spec中混入了多个 componentDidMount 方法,结果将会按顺序执行;而混入多个自定义的方法,因为缺少了 DEFINE_MANY 的描述,将会抛出异常。

最后几行是给某些属性或方法添加displayName字段便于区分,经过那么多if else条件语句筛选,这里所说的某些属性或方法基本就是指在 ReactClassInterface 中定义的但不存在于 RESERVED_SPEC_KEYS 中的属性或方法。关于displayName,如果在构建环境时用了webpack并且引入babel,那么其中的 babel-preset-react 会去引入一个叫 babel-plugin-transform-react-display-name 的模块,这个模块会自动的为你的组件混入一个displayName属性,值为你定义的变量的值。如 var foo = React.createClass({}),那么添加的displayName就是foo。

至此,mixSpecIntoComponent 的过程大致的都描述完了,我们可以来理一理这里的处理顺序:

  1. 首先处理mixins,如果存在mixins,则递归调用 mixSpecIntoComponent 方法。
  2. 遍历要混入的spec,首先判断是否是在 RESERVED_SPEC_KEYS 中定义的属性,如果是,则调用 RESERVED_SPEC_KEYS 中的相应方法处理,否则步骤3。
  3. 如果混入的spec未在 RESERVED_SPEC_KEYS 中定义,则判断它是否该被自动绑定,判断条件是:是function类型,未在 ReactClassInterface 中定义,已经存在于prototype中并且spec.autobind!==false。满足这个条件的基本上也就是我们自定义的一些方法了。如果满足条件,则将其添加到绑定的队列,并在prototype上添加,若不满足条件,则进入步骤4。
  4. 若不满足自动绑定条件,那么先判断是否已经存在于prototype中。若是,那么就要根据相应的规则,诸如 DEFINE_MANY 等描述来做相应的操作。我们自定义的方法是不能被多次定义的,只有一些生命周期方法可以,具体看 ReactClassInterface 的定义。如果还未存在于prototype中,见步骤5。
  5. 此时,有两种情况,可能是自定义的属性(不是function类型)或者是 ReactClassInterface 中定义的属性或方法。若是自定义属性,则将其添加到prototype上;若是 ReactClassInterface 中定义的属性或方法,则根据相应的判断来决定是否给该属性和方法添加 displayName 来便于区分。

接着回到 createClass 方法,继续往下看。

执行完 mixSpecIntoComponent 之后,54行执行 getDefaultProps 并把结果赋给了 Constructor 的一个defaultProps 静态属性。由此可知,getDefaultProps 会先于 getInitialState 执行,后者是在31行创建Constructor实例时才被调用。因此,不要在 getDefaultProps 中使用 this.state 属性,因为此时 state 还未被赋值。

最后我们来看77行,给 Constructor.prototype 的某些属性赋了一个null,这些属性是在 ReactClassInterface 上定义但未在 Constructor.prototype 上定义的。这么做的目的根据我个人的理解,是因为在首次挂在组件的时候,会先去判断 Constructor.prototype.componentWillMount 是否存在,若存在则调用。如果该属性在 Constructor.prototype 上未定义,那么我们必须遍历完整个prototype对象才可以得出结论;然而在prototype上赋了一个null,可以有效地防止不必要的遍历。这同样适用于组件更新时执行componentWillUpdate等方法,且效果更为明显,因为更新组件的操作在React中是相当频繁的。

createClass的过程到此就执行完了,我们再来回头看看Constructor构造函数,它在创建实例对象时还会去做一件事,bindAutoBindMethods,见21行。这个方法主要是使用bind方法绑定作用域到Constructor实例上的,因此在使用createClass时不必担心方法作用域的问题,而使用es6创建组件时,必须自己去bind作用域。那么 this.__reactAutoBindPairs 里面的值是哪里来的呢?不错,就是在执行 mixSpecIntoComponent 这个方法时,通过对 autoBindPairs 变量进行操作,从而影响了 __reactAutoBindPairs。

对比es6 extends写法

说了一堆,其实 createClass 主要就做了这么几件事。

  1. 生成并返回一个Constructor构造函数,该函数有props,context,refs,updater和state5个属性。
  2. 绑定了作用域。
  3. 将属性或方法添加到 Constructor.prototype 上。
  4. 调用 getDefaultProps 初始化 Constructor.defaultProps 属性。

以上3点通过 class xxx extends React.Component 写法都能办到。React.Component 即为 ReactComponent 对象,上面已经放过代码了,它正好是含有props,context,refs,updater这4个属性的构造函数,并且拥有 setState 和 forceUpdate 方法供我们调用,因此我们在创建组件时(stateless component除外),只需在constructor添加 this.state 用来初始化state属性即可保持一致。第2点需要注意,es6的写法并没有为你绑定作用域,需要自己手动在constructor中绑定,或者使用箭头函数的特性。第3点,在class中定义的方法都会被添加到prototype上,也可以满足条件。至于第4点,若需要定义defalutProps,只需手动添加,如:XXX.defalutProps 定义即可。唯一缺少的就是对mixins的支持。其实mixins已经在官方文档上被定义为一个大坑,主要在于mixins 与 mixins,mixins与组件之间存在着千丝万缕的依赖关系,代码量一增加,就变得十分难维护。还有一点,正如上面分析,mixins会造成命名的冲突,当我们定义两个同名的自定义方法时会抛异常。官方已经给出了一个称之为‘高阶组件’的解决方案来替代mixins了。具体的文档请参见https://facebook.github.io/react/blog/2016/07/13/mixins-considered-harmful.html。既然mixins不建议再使用了,那么 createClass 中 mixSpecIntoComponent 这个方法所做的很多事情都显得多余了,因此 es6 extends的写法以其代码的简洁性取代createClass应该是理所当然的事。

结语

第一次写博客,不知道是否已经把该讲的东西都讲清楚了,希望看过的小伙伴都能给点意见,或者是博主有什么理解误区的,都欢迎指出。接下来还会陆续对ReactElement,组件的mount和update等进行详细的说明,感谢各位赏脸阅读。