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。

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

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


react 中的高阶组件主要是对于 hooks 之前的类组件来说的,如果组件之中有复用的代码,需要重新创建一个父类,父类中存储公共代码,返回子类,同时把公用属性...
我们上一节了解了组件的更新机制,但是只是停留在表层上,例如我们的 setState 函数式同步执行的,我们的事件处理直接绑定在了 dom 元素上,这些都跟 re...
我们上一节了解了 react 的虚拟 dom 的格式,如何把虚拟 dom 转为真实 dom 进行挂载。其实函数是组件和类组件也是在这个基础上包裹了一层,一个是调...
react 本身提供了克隆组件的方法,但是平时开发中可能很少使用,可能是不了解。我公司的项目就没有使用,但是在很多三方库中都有使用。本小节我们来学习下如果使用该...
mobx 是一个简单可扩展的状态管理库,中文官网链接。小编在接触 react 就一直使用 mobx 库,上手简单不复杂。
我们在平常的开发中不可避免的会有很多列表渲染逻辑,在 pc 端可以使用分页进行渲染数限制,在移动端可以使用下拉加载更多。但是对于大量的列表渲染,特别像有实时数据...
本小节开始前,我们先答复下一个同学的问题。上一小节发布后,有小伙伴后台来信问到:‘小编你只讲了类组件中怎么使用 ref,那在函数式组件中怎么使用呢?’。确实我们...
上一小节我们了解了固定高度的滚动列表实现,因为是固定高度所以容器总高度和每个元素的 size、offset 很容易得到,这种场景也适合我们常见的大部分场景,例如...
上一小节我们处理了 setState 的批量更新机制,但是我们有两个遗漏点,一个是源码中的 setState 可以传入函数,同时 setState 可以传入第二...
我们知道 react 进行页面渲染或者刷新的时候,会从根节点到子节点全部执行一遍,即使子组件中没有状态的改变,也会执行。这就造成了性能不必要的浪费。之前我们了解...
在平时工作中的某些场景下,你可能想在整个组件树中传递数据,但却不想手动地通过 props 属性在每一层传递属性,contextAPI 应用而生。
楼主最近入职新单位了,恰好新单位使用的技术栈是 react,因为之前一直进行的是 vue2/vue3 和小程序开发,对于这些技术栈实现机制也有一些了解,最少面试...
我们上一节了了解了函数式组件和类组件的处理方式,本质就是处理基于 babel 处理后的 type 类型,最后还是要处理虚拟 dom。本小节我们学习下组件的更新机...
前面几节我们学习了解了 react 的渲染机制和生命周期,本节我们正式进入基本面试必考的核心地带 -- diff 算法,了解如何优化和复用 dom 操作的,还有...
我们在之前已经学习过 react 生命周期,但是在 16 版本中 will 类的生命周期进行了废除,虽然依然可以用,但是需要加上 UNSAFE 开头,表示是不安...
上一小节我们学习了 react 中类组件的优化方式,对于 hooks 为主流的函数式编程,react 也提供了优化方式 memo 方法,本小节我们来了解下它的用...
开源不易,感谢你的支持,❤ star me if you like concent ^_^
hel-micro,模块联邦sdk化,免构建、热更新、工具链无关的微模块方案 ,欢迎关注与了解
本文主题围绕concent的setup和react的五把钩子来展开,既然提到了setup就离不开composition api这个关键词,准确的说setup是由...
ReactsetState的执行是异步还是同步官方文档是这么说的setState()doesnotalwaysimmediatelyupdatethecomponent.Itmaybatchordefertheupdateuntillater.Thismakesreadingthis.staterightaftercallingsetState()apotentialpitfall.Instead,usecom