`

ReactClass

 
阅读更多

ReactClass用于创建自定义组件的构造函数。

 

'use strict';

var _prodInvariant = require('./reactProdInvariant'),// 生产环境React形式带url报错
    _assign = require('object-assign');

// 基本的组件构造函数,内部实现setState、forceUpdate方法,用于更新state属性,重绘组件
var ReactComponent = require('./ReactComponent');

var ReactElement = require('./ReactElement');

// 用于区分校验数据类型prop、context、childContext
var ReactPropTypeLocationNames = require('./ReactPropTypeLocationNames');

// 组件尚未获得参数updater,即"react-dom"包下的"ReactUpdateQueue",调用setState、replaceState、forceUpdate时警告  
var ReactNoopUpdateQueue = require('./ReactNoopUpdateQueue');

var emptyObject = require('fbjs/lib/emptyObject');

// invariant(condition,format,a,b,c,d,e,f) condition为否值,替换format中的"%s",并throw error报错  
var invariant = require('fbjs/lib/invariant');

// warning(condition,format) condition为否值,替换format中的"%s",并console.error警告   
var warning = require('fbjs/lib/warning');

var MIXINS_KEY = 'mixins';

// 传参fn设置了name属性,identity函数将清除该name属性,设为空字符串
function identity(fn) {
  return fn;
}

var injectedMixins = [];

// ReactClass组件构造函数中的方法允许被定义的次数
//    多次定义使用React.createClass(spec={mixins:[fn]})实现
//    childContextTypes、contextTypes、getDefaultProps、propTypes、statics通过RESERVED_SPEC_KEYS添加到组件实例中
// 'DEFINE_MANY',方法或属性允许定义多次
// 'DEFINE_MANY_MERGED',方法可多次定义,并将多个返回值复合为一个;每个返回值不允许存在同名属性
var ReactClassInterface = {
  mixins: 'DEFINE_MANY',
  statics: 'DEFINE_MANY',
  propTypes: 'DEFINE_MANY',
  contextTypes: 'DEFINE_MANY',
  childContextTypes: 'DEFINE_MANY',
  getDefaultProps: 'DEFINE_MANY_MERGED',
  getInitialState: 'DEFINE_MANY_MERGED',
  getChildContext: 'DEFINE_MANY_MERGED',
  render: 'DEFINE_ONCE',
  componentWillMount: 'DEFINE_MANY',
  componentDidMount: 'DEFINE_MANY',
  componentWillReceiveProps: 'DEFINE_MANY',
  shouldComponentUpdate: 'DEFINE_ONCE',
  componentWillUpdate: 'DEFINE_MANY',
  componentDidUpdate: 'DEFINE_MANY',
  componentWillUnmount: 'DEFINE_MANY',
  updateComponent: 'OVERRIDE_BASE'
};

// 约定自定义ReactClass组件添加原型方法或属性的方式
var RESERVED_SPEC_KEYS = {
  // 将React.createClass(spec})传参spec的displayName属性添加为组件的静态属性
  displayName: function (Constructor, displayName) {
    Constructor.displayName = displayName;
  },

  // 将React.createClass(spec)传参的spec.mixins属性[{key:fn}]复合为组件的原型属性或方法
  mixins: function (Constructor, mixins) {
    if (mixins) {
      for (var i = 0; i < mixins.length; i++) {
        mixSpecIntoComponent(Constructor, mixins[i]);
      }
    }
  },

  // 复合React.createClass({mixins:[childContextTypes]})中的多个childContextTypes定义,作为组件的静态属性
  childContextTypes: function (Constructor, childContextTypes) {
    if (process.env.NODE_ENV !== 'production') {
      // validateTypeDef函数,用于确保props、childContext、context的校验器为函数形式
      validateTypeDef(Constructor, childContextTypes, 'childContext');
    }
    Constructor.childContextTypes = _assign({}, Constructor.childContextTypes, childContextTypes);
  },

  // 复合React.createClass({mixins:[contextTypes]})中的多个contextTypes定义,作为组件的静态属性
  contextTypes: function (Constructor, contextTypes) {
    if (process.env.NODE_ENV !== 'production') {
      validateTypeDef(Constructor, contextTypes, 'context');
    }
    Constructor.contextTypes = _assign({}, Constructor.contextTypes, contextTypes);
  },

  // 复合React.createClass({mixins:[propTypes]})中的多个propTypes定义,作为组件的静态属性
  propTypes: function (Constructor, propTypes) {
    if (process.env.NODE_ENV !== 'production') {
      validateTypeDef(Constructor, propTypes, 'prop');
    }
    Constructor.propTypes = _assign({}, Constructor.propTypes, propTypes);
  },

  // 复合getDefaultProps的多次定义,每次定义的返回值不允许相同,添加为静态方法
  getDefaultProps: function (Constructor, getDefaultProps) {
    if (Constructor.getDefaultProps) {
      Constructor.getDefaultProps = createMergedResultFunction(Constructor.getDefaultProps, getDefaultProps);
    } else {
      Constructor.getDefaultProps = getDefaultProps;
    }
  },

  // 将React.createClass(spec)传参的spec.statics添加为组件的静态属性
  statics: function (Constructor, statics) {
    mixStaticSpecIntoComponent(Constructor, statics);
  },

  // React.createClass(spec={autobind:fn})传参spec.autobind不对组件构造函数产生任何影响
  // 只用于判断部分原型方法是否将this关键字指向组件实例
  autobind: function () {} 
};

// 确保props、childContext、context的校验器为函数形式
function validateTypeDef(Constructor, typeDef, location) {
  for (var propName in typeDef) {
    if (typeDef.hasOwnProperty(propName)) {
      process.env.NODE_ENV !== 'production' ? 
        warning(typeof typeDef[propName] === 'function', 
          '%s: %s type `%s` is invalid; it must be a function, usually from ' 
          + 'React.PropTypes.', 
          Constructor.displayName || 'ReactClass', ReactPropTypeLocationNames[location], propName) 
        : void 0;
    }
  }
}

// 校验React.createClass(spec)创建组件的构造函数是否重写不允许定义多次的方法
function validateMethodOverride(isAlreadyDefined, name) {
  var specPolicy = ReactClassInterface.hasOwnProperty(name) ? ReactClassInterface[name] : null;

  // React.createClass(spec)的传参spec试图重写replaceState、isMount方法
  if (ReactClassMixin.hasOwnProperty(name)) {
    !(specPolicy === 'OVERRIDE_BASE') ? 
      process.env.NODE_ENV !== '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;
  }

  // 不是ReactClassInterface约定可以定义多次的原型方法,报错
  if (isAlreadyDefined) {
    !(specPolicy === 'DEFINE_MANY' || specPolicy === 'DEFINE_MANY_MERGED') ? 
      process.env.NODE_ENV !== '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;
  }
}

// 将React.createClass(spec)的传参spec添加为组件的原型方法或属性
function mixSpecIntoComponent(Constructor, spec) {
  // 传入React.createClass(spec)的参数不是对象或null时,警告
  if (!spec) {
    if (process.env.NODE_ENV !== 'production') {
      var typeofSpec = typeof spec;
      var isMixinValid = typeofSpec === 'object' && spec !== null;

      process.env.NODE_ENV !== '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;
    }

    return;
  }

  // 传参spec为函数或reactElement时,报错
  !(typeof spec !== 'function') ? 
    process.env.NODE_ENV !== '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;
  !!ReactElement.isValidElement(spec) ? 
    process.env.NODE_ENV !== 'production' ? 
      invariant(false, 'ReactClass: You\'re attempting to use a component as a mixin.' 
        + ' Instead, just use a regular object.') 
      : _prodInvariant('76') 
    : void 0;

  var proto = Constructor.prototype;
  var autoBindPairs = proto.__reactAutoBindPairs;

  // spec.mixins存在时,RESERVED_SPEC_KEYS.mixins方法添加组件的原型属性与方法
  if (spec.hasOwnProperty(MIXINS_KEY)) {
    RESERVED_SPEC_KEYS.mixins(Constructor, spec.mixins);
  }

  for (var name in spec) {
    // 不是spec对象自有属性,跳过
    if (!spec.hasOwnProperty(name)) {
      continue;
    }

    // mixins属性跳过
    if (name === MIXINS_KEY) {
      continue;
    }

    var property = spec[name];
    var isAlreadyDefined = proto.hasOwnProperty(name);// 判断原型属性是否已经存在name属性

    // 校验React.createClass(spec)创建组件的构造函数是否重写不允许定义多次的方法
    validateMethodOverride(isAlreadyDefined, name);

    // 调用RESERVED_SPEC_KEYS[name]给用户自定义ReactClass组件添加原型方法或属性
    // 包含displayName、childContextTypes、contextTypes、getDefaultProps、propTypes、statics、autobind
    if (RESERVED_SPEC_KEYS.hasOwnProperty(name)) {
      RESERVED_SPEC_KEYS[name](Constructor, property);

    // 包含getInitialState、getChildContext、render、componentWillMount、componentDidMount
    // componentWillReceiveProps、shouldComponentUpdate、componentWillUpdate、componentDidUpdate
    // componentWillUnmount、updateComponent,及ReactClassInterface不包含的方法
    } else {
      var isReactClassMethod = ReactClassInterface.hasOwnProperty(name);
      var isFunction = typeof property === 'function';

      // spec[name]是函数,且不是ReactClassInterface中的方法名,且原型方法中为定义,且spec.autobind为真值
      // 调用spec[name]方法时,以组件实例作为this关键字
      var shouldAutoBind = isFunction && !isReactClassMethod && !isAlreadyDefined && spec.autobind !== false;

      // 用户自定义方法,即不是ReactClassInterface内由react机制在生命周期中调用的方法
      // autoBindPairs缓存需要将this关键字指向组件实例的方法名及函数体,实例化时bind组件实例
      if (shouldAutoBind) {
        autoBindPairs.push(name, property);
        proto[name] = property;

      // ReactClassInterface内设定的方法名,或spec.autobind为否值时的自定义方法
      } else {
        // 再次定义,对ReactClassInterface内设定的方法名有效,用户自定义方法不会重复定义
        if (isAlreadyDefined) {
          var specPolicy = ReactClassInterface[name];

          // validateMethodOverride函数校验原型方法允许重复定义的次数时,本当捕获的错误,再次校验
          !(isReactClassMethod && (specPolicy === 'DEFINE_MANY_MERGED' || specPolicy === 'DEFINE_MANY')) ? 
            process.env.NODE_ENV !== 'production' ? 
              invariant(false, 'ReactClass:' 
                + ' Unexpected spec policy %s for key %s when mixing in component specs.', specPolicy, name) 
              : _prodInvariant('77', specPolicy, name) 
            : void 0;

          // getInitialState、getChildContext可多次定义,并复合每次定义的返回值
          if (specPolicy === 'DEFINE_MANY_MERGED') {
            // createMergedResultFunction(one,two)
            // 复合one、two函数的执行结果,one、two函数的返回值不允许同名属性多次赋值
            proto[name] = createMergedResultFunction(proto[name], property);

          } else if (specPolicy === 'DEFINE_MANY') {
            // createChainedFunction(one,two),顺序执行one、two函数,其返回值无实意
            proto[name] = createChainedFunction(proto[name], property);
          }

        // 首次定义,添加原型方法的同时,原型方法获得displayName属性,调试用
        } else {
          proto[name] = property;
          if (process.env.NODE_ENV !== 'production') {
            if (typeof property === 'function' && spec.displayName) {
              proto[name].displayName = spec.displayName + '_' + name;
            }
          }
        }
      }
    }
  }
}

// 添加组件的静态属性
function mixStaticSpecIntoComponent(Constructor, statics) {
  if (!statics) {
    return;
  }
  for (var name in statics) {
    var property = statics[name];
    if (!statics.hasOwnProperty(name)) {
      continue;
    }

    // displayName、childContextTypes、contextTypes、getDefaultProps、propTypes、statics、autobind不能置入spec.statics中
    var isReserved = name in RESERVED_SPEC_KEYS;
    !!isReserved ? process.env.NODE_ENV !== 'production' ? 
      invariant(false, 'ReactClass: You are attempting to define a reserved property, `%s`,' 
        + ' that shouldn\'t be on the "statics" key. Define it as an instance property instead;' 
        + ' it will still be accessible on the constructor.', name) 
      : _prodInvariant('78', name) : void 0;

    // 组件的静态属性不能重复定义
    var isInherited = name in Constructor;
    !!isInherited ? 
      process.env.NODE_ENV !== 'production' ? 
        invariant(false, 'ReactClass: You are attempting to define `%s` on your component more than once.' 
          + ' This conflict may be due to a mixin.', name) 
        : _prodInvariant('79', name) 
      : void 0;

    Constructor[name] = property;
  }
}

// 将two的属性拷贝给one,不允许同名属性
function mergeIntoWithNoDuplicateKeys(one, two) {
  // 参数one、two必须是对象
  !(one && two && typeof one === 'object' && typeof two === 'object') ? 
    process.env.NODE_ENV !== 'production' ? 
      invariant(false, 'mergeIntoWithNoDuplicateKeys(): Cannot merge non-objects.') 
      : _prodInvariant('80') 
    : void 0;

  // 复合同名属性时报错
  for (var key in two) {
    if (two.hasOwnProperty(key)) {
      !(one[key] === undefined) ? 
        process.env.NODE_ENV !== '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;
      one[key] = two[key];
    }
  }
  return one;
}

// 复合one、two函数的执行结果,one、two函数的返回值不允许同名属性多次赋值
function createMergedResultFunction(one, two) {
  return function mergedResult() {
    var a = one.apply(this, arguments);
    var b = two.apply(this, arguments);
    if (a == null) {
      return b;
    } else if (b == null) {
      return a;
    }
    var c = {};

    // mergeIntoWithNoDuplicateKeys(one,two),将two的属性拷贝给one,不允许同名属性
    mergeIntoWithNoDuplicateKeys(c, a);
    mergeIntoWithNoDuplicateKeys(c, b);
    return c;
  };
}

// 顺序执行one、two函数,其返回值无实意
function createChainedFunction(one, two) {
  return function chainedFunction() {
    one.apply(this, arguments);
    two.apply(this, arguments);
  };
}

// 使method函数的this关键字指向组件实例,且提供bind属性用于设置特定的参数,可实现工厂化调用
function bindAutoBindMethod(component, method) {
  var boundMethod = method.bind(component);
  if (process.env.NODE_ENV !== 'production') {
    boundMethod.__reactBoundContext = component;
    boundMethod.__reactBoundMethod = method;
    boundMethod.__reactBoundArguments = null;
    var componentName = component.constructor.displayName;
    var _bind = boundMethod.bind;// 缓存浏览器原生的bind方法,以执行函数为this关键字,即boundMethod
    boundMethod.bind = function (newThis) {
      // args赋值为次参以后的所有参数
      for (var _len = arguments.length, args = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
        args[_key - 1] = arguments[_key];
      }

      // 校验this关键字指向组件实例,且校验boundMethod已实现给method函数注入了特定的参数
      if (newThis !== component && newThis !== null) {
        process.env.NODE_ENV !== 'production' ? 
          warning(false, 'bind(): React component methods may only be bound to the ' 
            + 'component instance. See %s', componentName) 
          : void 0;
      } else if (!args.length) {
        process.env.NODE_ENV !== 'production' ? 
          warning(false, 'bind(): You are binding a component method to the component. ' 
            + 'React does this for you automatically in a high-performance ' 
            + 'way, so you can safely remove this call. See %s', componentName) 
          : void 0;
        return boundMethod;
      }

      // 调用浏览器原生的bind函数,改变this关键字指向,添加特定参数
      var reboundMethod = _bind.apply(boundMethod, arguments);
      reboundMethod.__reactBoundContext = component;
      reboundMethod.__reactBoundMethod = method;
      reboundMethod.__reactBoundArguments = args;
      return reboundMethod;
    };
  }
  return boundMethod;
}

// 使原型方法的this关键字指向组件实例component
function bindAutoBindMethods(component) {
  var pairs = component.__reactAutoBindPairs;
  for (var i = 0; i < pairs.length; i += 2) {
    var autoBindKey = pairs[i];
    var method = pairs[i + 1];
    component[autoBindKey] = bindAutoBindMethod(component, method);
  }
}

var ReactClassMixin = {
  // 替换state,内部调用ReactUpdates.enqueueUpdate添加脏组件,并重绘组件,执行回调
  replaceState: function (newState, callback) {
    this.updater.enqueueReplaceState(this, newState);
    if (callback) {
      this.updater.enqueueCallback(this, callback, 'replaceState');
    }
  },

  // 判断组件是否执行render方法,完成挂载
  isMounted: function () {
    return this.updater.isMounted(this);
  }
};

// 用于继承ReactComponent模块中的setState、forceUpdate方法,获得ReactClassMixin的replaceState、isMounted方法
var ReactClassComponent = function () {};
_assign(ReactClassComponent.prototype, ReactComponent.prototype, ReactClassMixin);

var ReactClass = {
  // 用于创建用户自定义组件ReactComponent的构造函数
  createClass: function (spec) {
    // 组件的构造函数
    // 参数updater通常是"react-dom"包下的"ReactUpdateQueue"模块,用于实现setState等方法的重绘组件功能
    var Constructor = identity(function (props, context, updater) {

      // 构造函数作为普通函数调用时,警告
      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;
      }

      // spec[name]是函数,且不是ReactClassInterface中的方法名,且原型方法中为定义,且spec.autobind为真值
      // 调用spec[name]方法时,以组件实例作为this关键字
      if (this.__reactAutoBindPairs.length) {
        bindAutoBindMethods(this);
      }

      this.props = props;
      this.context = context;// 可用于传递给子组件
      this.refs = emptyObject;// 子组件的引用

      // 组件实例化时将this.updater赋值为"react-dom"包下的"ReactUpdateQueue"
      // 用于实现setState、replaceState、forceUpdate方法,更新state、重绘组件
      // ReactNoopUpdateQueue实现组件未完成挂载时调用setState方法触发重绘报错功能
      this.updater = updater || ReactNoopUpdateQueue;

      this.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;
        }
      }

      // 初始化state不是对象,报错
      !(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模块中的setState、forceUpdate方法,并获得ReactClassMixin的replaceState、isMounted方法
    Constructor.prototype = new ReactClassComponent();

    Constructor.prototype.constructor = Constructor;

    // 存放待将this关键字指向组价实例的用户自定义方法
    Constructor.prototype.__reactAutoBindPairs = [];

    // 公共原型方法通过mixSpecIntoComponent函数赋值给组件
    injectedMixins.forEach(mixSpecIntoComponent.bind(null, Constructor));

    // 将React.createClass(spec)的传参spec添加为组件的原型方法或属性
    mixSpecIntoComponent(Constructor, spec);

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

    // 可定义getDefaultProps静态方法、getInitialState原型方法的标识
    // ReactCompositeComponent模块实例化组件时,用于校验单纯继承ReactComponent的组件不能拥有这两个方法
    if (process.env.NODE_ENV !== 'production') {
      if (Constructor.getDefaultProps) {
        Constructor.getDefaultProps.isReactClassApproved = {};
      }
      if (Constructor.prototype.getInitialState) {
        Constructor.prototype.getInitialState.isReactClassApproved = {};
      }
    }

    // render方法不能缺省
    !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;
    }

    // 出于减少运行时间的考虑
    for (var methodName in ReactClassInterface) {
      if (!Constructor.prototype[methodName]) {
        Constructor.prototype[methodName] = null;
      }
    }

    return Constructor;
  },

  // 公共原型方法设置,用于添加每个组件中的原型方法或部分静态方法,对外不提供接口
  injection: {
    injectMixin: function (mixin) {
      injectedMixins.push(mixin);
    }
  }

};

module.exports = ReactClass;

 

0
0
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics