react源码解读 {createClass}

一个框架源码的解读,既有利于更深入地了解框架,使用上更得心应手,又可以学习到其中代码组织的思路,吸收其精华简洁的写法以便于日常工作上使用。下面我就挑选近年大热门react(15.3.1),从中剖析框架的设计思路,由浅入深地学习。

我们从这个文件开始看起,这是react的主入口(./lib/react.js)。

/**
 * copyright 2013-present,Facebook,Inc.
 * All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 *
 * @providesModule React
 */

'use strict';

var _assign = require('object-assign');

var ReactChildren = require('./ReactChildren');
var ReactComponent = require('./ReactComponent');
var ReactPureComponent = require('./ReactPureComponent');
var ReactClass = require('./ReactClass');
var ReactDOMFactories = require('./ReactDOMFactories');
var ReactElement = require('./ReactElement');
var ReactPropTypes = require('./ReactPropTypes');
var ReactVersion = require('./ReactVersion');

var onlyChild = require('./onlyChild');
var warning = require('fbjs/lib/warning');

var createElement = ReactElement.createElement;
var createFactory = ReactElement.createFactory;
var cloneElement = ReactElement.cloneElement;

if (process.env.NODE_ENV !== 'production') {
  var ReactElementValidator = require('./ReactElementValidator');
  createElement = ReactElementValidator.createElement;
  createFactory = ReactElementValidator.createFactory;
  cloneElement = ReactElementValidator.cloneElement;
}

var __spread = _assign;

if (process.env.NODE_ENV !== 'production') {
  var warned = false;
  __spread = function () {
    process.env.NODE_ENV !== 'production' ? warning(warned,'React.__spread is deprecated and should not be used. Use ' + 'Object.assign directly or another helper function with similar ' + 'semantics. You may be seeing this warning due to your compiler. ' + 'See https://fb.me/react-spread-deprecation for more details.') : void 0;
    warned = true;
    return _assign.apply(null,arguments);
  };
}

var React = {

  // Modern

  Children: {
    map: ReactChildren.map,forEach: ReactChildren.forEach,count: ReactChildren.count,toArray: ReactChildren.toArray,only: onlyChild
  },Component: ReactComponent,PureComponent: ReactPureComponent,createElement: createElement,cloneElement: cloneElement,isValidElement: ReactElement.isValidElement,// Classic

  PropTypes: ReactPropTypes,createClass: ReactClass.createClass,createFactory: createFactory,createMixin: function (mixin) {
    // Currently a noop. Will be used to validate and trace mixins.
    return mixin;
  },// This looks DOM specific but these are actually isomorphic helpers
  // since they are just generating DOM strings.
  DOM: ReactDOMFactories,version: ReactVersion,// Deprecated hook for JSX spread,don't use this for anything.
  __spread: __spread
};

module.exports = React;

我们直接跳过前面的环境判断以及模块引入,可以看到从50行起就是React的关键代码。并且我们可以清晰的从上面看到React所提供的方法。这是离我们使用者最近的一层,看到信息量不多。我们就按照开发的思路,一步一步地深入源码。
编写一个组件,当然是从创建开始,我们使用的是 React.createClass,不难发现,React.createClass实际上引用的是ReactClass.createClass。当然我们也可以用ES6的写法直接继承至React.Component.这两种写法有什么差异存在,我们先把悬念放在后面。
先从createClass的源码看起(./lib/ReactClass)。

var ReactClass = {

  /**
   * Creates a composite component class given a class specification.
   * See https://facebook.github.io/react/docs/top-level-api.html#react.createclass
   *
   * @param {object} spec Class specification (which must define `render`).
   * @return {function} Component constructor function.
   * @public
   */
  createClass: function (spec) {
    var Constructor = function (props,context,updater) {
      // This constructor gets overridden by mocks. The argument is used
      // by mocks to assert on what gets mounted.

      if (process.env.NODE_ENV !== 'production') {
        process.env.NODE_ENV !== '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;
      }

      // Wire up auto-binding
      if (this.__reactAutoBindPairs.length) {
        bindAutoBindMethods(this);
      }

      this.props = props;
      this.context = context;
      this.refs = emptyObject;
      this.updater = updater || ReactNoopUpdateQueue;

      this.state = null;

      // ReactClasses doesn't have constructors. Instead,they use the
      // getinitialState and componentwillMount methods for initialization.

      var initialState = this.getinitialState ? this.getinitialState() : null;
      if (process.env.NODE_ENV !== 'production') {
        // We allow auto-mocks to proceed as if they're returning null.
        if (initialState === undefined && this.getinitialState._isMockFunction) {
          // This is probably bad practice. Consider warning here and
          // deprecating this convenience.
          initialState = null;
        }
      }
      !(typeof initialState === 'object' && !Array.isArray(initialState)) ? process.env.NODE_ENV !== 'production' ? invariant(false,'%s.getinitialState(): must return an object or null',Constructor.displayName || 'ReactCompositeComponent') : _prodInvariant('82',Constructor.displayName || 'ReactCompositeComponent') : void 0;

      this.state = initialState;
    };
    Constructor.prototype = new ReactClassComponent();
    Constructor.prototype.constructor = Constructor;
    Constructor.prototype.__reactAutoBindPairs = [];

    injectedMixins.forEach(mixSpecIntoComponent.bind(null,Constructor));

    mixSpecIntoComponent(Constructor,spec);

    // Initialize the defaultProps property after all mixins have been merged.
    if (Constructor.getDefaultProps) {
      Constructor.defaultProps = Constructor.getDefaultProps();
    }

    if (process.env.NODE_ENV !== 'production') {
      // This is a tag to indicate that the use of these method names is ok,// since it's used with createClass. If it's not,then it's likely a
      // mistake so we'll warn you to use the static property,property
      // initializer or constructor respectively.
      if (Constructor.getDefaultProps) {
        Constructor.getDefaultProps.isReactClassApproved = {};
      }
      if (Constructor.prototype.getinitialState) {
        Constructor.prototype.getinitialState.isReactClassApproved = {};
      }
    }

    !Constructor.prototype.render ? process.env.NODE_ENV !== 'production' ? invariant(false,'createClass(...): Class specification must implement a `render` method.') : _prodInvariant('83') : void 0;

    if (process.env.NODE_ENV !== 'production') {
      process.env.NODE_ENV !== '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;
      process.env.NODE_ENV !== 'production' ? warning(!Constructor.prototype.componentwillRecieveProps,'%s has a method called ' + 'componentwillRecieveProps(). Did you mean componentwillReceiveProps()?',spec.displayName || 'A component') : void 0;
    }

    // Reduce time spent doing lookups by setting these on the prototype.
    for (var methodName in ReactClassInterface) {
      if (!Constructor.prototype[methodName]) {
        Constructor.prototype[methodName] = null;
      }
    }

    return Constructor;
  },injection: {
    injectMixin: function (mixin) {
      injectedMixins.push(mixin);
    }
  }

};

644行起,createClass方法首先定义了一个Constructor构造函数,折叠内部,我们看看这个方法在返回一个构造函数前做了什么,
直接跳到681行,构造函数的prototype指向一个ReactClassComponent的实例。

Constructor.prototype = new ReactClassComponent();

往上翻我们可以发现,ReactClassComponent的prototype属性,拷贝了ReactComponent.prototype 和 ReactClassMixin,因此我们的组件可以使用ReactComponent原型上的方法

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

683行到687行。
定义了 __reactAutoBindPairs一个空数组。
先将mixin里面的方法按照key,function内容的顺序成对存入 __reactAutoBindPairs,
接着就是spec对象里的方法用同样的方式存入。

Constructor.prototype.__reactAutoBindPairs = [];

    injectedMixins.forEach(mixSpecIntoComponent.bind(null,spec);

690行我们可以看到Constructor.defaultProps 就是我们开发中 getDefaultProps()所返回的对象。

if (Constructor.getDefaultProps) {
      Constructor.defaultProps = Constructor.getDefaultProps();
    }

694行 -- 712行 是在开发环境中对开发者的建议,以及规范使用的警示。
715行 -- 719行 可以知道我们创建一个组件需要定义的方法都在ReactClassInterface上有,当前未定义的方法设置为空,我们就可以通过打印组件的prototype属性清楚地在日志上知道我们有哪些api是未定义的。通过设置未定义的属性为空,可以减少程序查找的时间。
721行 最终返回了这个封装好的构造函数

for (var methodName in ReactClassInterface) {
      if (!Constructor.prototype[methodName]) {
        Constructor.prototype[methodName] = null;
      }
    }

    return Constructor;

看到这里我们可以明白一点,组件实质上是一个构造函数,而我们自定义方法,既存在了prototype里,也按照[key,content,key,content...]的方式归纳到了Constructor.prototype.__reactAutoBindPairs 里。这是为了组件实例化时可以将这些方法直接遍历绑定在实例上,并且避免了React官方指定的方法也被绑定在实例上。

接下来我们展开645行的Constructor,可以看到实例化的时候主要做了两件事。
654行
第一件事就是将上文提到的存在Constructor.prototype.__reactAutoBindPairs内容成对取出,绑定在实例上。

if (this.__reactAutoBindPairs.length) {
        bindAutoBindMethods(this);
      }

668行 ——679行
第二件事就是判断组件是否有定义getinitialState,如果有,则将state设置为该方法返回的值,如果没有设置state为null。

var initialState = this.getinitialState ? this.getinitialState() : null;
      if (process.env.NODE_ENV !== 'production') {
        // We allow auto-mocks to proceed as if they're returning null.
        if (initialState === undefined && this.getinitialState._isMockFunction) {
          // This is probably bad practice. Consider warning here and
          // deprecating this convenience.
          initialState = null;
        }
      }
      !(typeof initialState === 'object' && !Array.isArray(initialState)) ? process.env.NODE_ENV !== 'production' ? invariant(false,Constructor.displayName || 'ReactCompositeComponent') : void 0;

      this.state = initialState;

到这里我们大概地知道了一个组件从创建构造函数到实例化的时候做了什么事情了。后续我们继续解读更底层的ReactComponent。

希望能对大家有帮助。如果有错误的地方,恳请各位大神指正。

相关文章

一、前言 在组件方面react和Vue一样的,核心思想玩的就是组件...
前言: 前段时间学习完react后,刚好就接到公司一个react项目...
前言: 最近收到组长通知我们项目组后面新开的项目准备统一技...
react 中的高阶组件主要是对于 hooks 之前的类组件来说的,如...
我们上一节了解了组件的更新机制,但是只是停留在表层上,例...
我们上一节了解了 react 的虚拟 dom 的格式,如何把虚拟 dom...