资讯专栏INFORMATION COLUMN

react源码解读 {createClass}

clasnake / 2806人阅读

摘要:行最终返回了这个封装好的构造函数。看到这里我们可以明白一点,组件实质上是一个构造函数,而我们自定义的方法,既存在了里,也按照的方式归纳到了里。到这里我们大概地知道了一个组件从创建构造函数到实例化的时候做了什么事情了。

对一个框架源码的解读,既有利于更深入地了解框架,使用上更得心应手,又可以学习到其中代码组织的思路,吸收其精华简洁的写法以便于日常工作上使用。下面我就挑选近年大热门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, Constructor));

    mixSpecIntoComponent(Constructor, 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, "%s.getInitialState(): must return an object or null", Constructor.displayName || "ReactCompositeComponent") : _prodInvariant("82", Constructor.displayName || "ReactCompositeComponent") : void 0;

      this.state = initialState;

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

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

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/81184.html

相关文章

  • 解读React源码(三):生命周期的管理艺术

    摘要:前言的主要思想是通过构建可复用组件来构建页面所谓组件其实就是有限状态机通过状态渲染对应的界面且每个组件都有自己的生命周期它规定了组件的状态和方法需要在哪个阶段改变和执行子组件子组件子组件子组件初探生命周期当首次挂载组件时按顺序执行当卸载组件 前言 React的主要思想是通过构建可复用组件来构建页面.所谓组件,其实就是有限状态机(FSM),通过状态渲染对应的界面,且每个组件都有自己的生命...

    lk20150415 评论0 收藏0
  • React源码解析 - ReactClass.js

    摘要:下面,我将从上到下挑选出能给自己一些启发的源码做注解。省略了很多代码通过这段精简的,我们看到,,返回的是一个构造函数,该函数原型继承于,并将参数中定义的属性合并到其中。 ReactClass.js 源文件: https://github.com/facebook/react/blob/master/src/isomorphic/classic/class/ReactClass.js 2...

    Big_fat_cat 评论0 收藏0
  • 深入 JavaScript 原型继承原理——babel 编译码解读

    摘要:目录无继承简单的字段声明无继承简单的方法声明简单继承一层继承字段覆盖无继承静态函数无继承静态变量神秘的类无继承简单的字段声明先来看个最简单的例子,我们仅仅使用了关键字并定义了一个变量最后编译出来的代码如下。无继承静态变量还有个小例子。 在[上一篇文章][]中,我们提到 ES6 的 class 语法糖是个近乎完美的方案,并且讲解了实现继承的许多内部机制,如 prototype/__pro...

    stdying 评论0 收藏0
  • React.js学习笔记之JSX解读

    摘要:学习笔记之解读前端技术不多说,大腿很粗什么是是的核心组成部分,它使用标记的方式去直接声明界面,界面组件之间可以互相嵌套。它的目的是通过各种编译器将这些标记编译成标准的语言。的标签与函数名都是使用的驼峰命名。目前,一个的,只能返回一个节点。 React.js学习笔记之JSX解读 @(前端技术) Why React? 不多说,Facebook大腿很粗 什么是JSX JSX是React的核心...

    tianlai 评论0 收藏0
  • React源码解析ReactDOM.render源码

    摘要:的创建组件,其实根源还是调用了编译之后一般写法建议用来进行源码的跟踪链接从源码角度来看创建一个组件的过程中发生了什么。 https://github.com/jimwmg/Rea... 1 React.createClass( ) var HelloWorld = React.createClass({ render : function(){ return ...

    joywek 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<