Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

createClass #27

Closed
RubyLouvre opened this issue Jul 7, 2017 · 2 comments
Closed

createClass #27

RubyLouvre opened this issue Jul 7, 2017 · 2 comments

Comments

@RubyLouvre
Copy link
Owner

RubyLouvre commented Jul 7, 2017

 var ReactComponent = React.Component;

  var _assign = Object.assign;

  var _invariant = function _invariant(condition, format, a, b, c, d, e, f) {
    if (!condition) {
      var error;
      if (format === undefined) {
        error = new Error(
          "Minified exception occurred; use the non-minified dev environment " +
            "for the full error message and additional helpful warnings."
        );
      } else {
        var args = [a, b, c, d, e, f];
        var argIndex = 0;
        error = new Error(
          format.replace(/%s/g, function() {
            return args[argIndex++];
          })
        );
        error.name = "Invariant Violation";
      }

      error.framesToPop = 1; // we don't care about invariant's own frame
      throw error;
    }
  };

  var MIXINS_KEY = "mixins";

  /**
 * Policies that describe methods in `ReactClassInterface`.
 */

  var injectedMixins = [];

  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"
  };

  /**
 * Mapping from class specification keys to special processing functions.
 *
 * Although these are declared like instance properties in the specification
 * when defining classes using `React.createClass`, they are actually static
 * and are accessible on the constructor instead of the prototype. Despite
 * being static, they must be defined outside of the "statics" key under
 * which all other static methods are defined.
 */
  var RESERVED_SPEC_KEYS = {
    displayName: function displayName(Constructor, _displayName) {
      return (Constructor.displayName = _displayName);
    },
    mixins: function mixins(Constructor, _mixins) {
      if (_mixins) {
        for (var i = 0; i < _mixins.length; i++) {
          mixSpecIntoComponent(Constructor, _mixins[i]);
        }
      }
    },
    childContextTypes: function childContextTypes(
      Constructor,
      _childContextTypes
    ) {
      return (Constructor.childContextTypes = _assign(
        {},
        Constructor.childContextTypes,
        _childContextTypes
      ));
    },
    contextTypes: function contextTypes(Constructor, _contextTypes) {
      return (Constructor.contextTypes = _assign(
        {},
        Constructor.contextTypes,
        _contextTypes
      ));
    },
    /**
   * Special case getDefaultProps which should move into statics but requires
   * automatic merging.
   */
    getDefaultProps: function getDefaultProps(Constructor, _getDefaultProps) {
      if (Constructor.getDefaultProps) {
        Constructor.getDefaultProps = createMergedResultFunction(
          Constructor.getDefaultProps,
          _getDefaultProps
        );
      } else {
        Constructor.getDefaultProps = _getDefaultProps;
      }
    },

    propTypes: function propTypes(Constructor, _propTypes) {
      Constructor.propTypes = _assign({}, Constructor.propTypes, _propTypes);
    },
    statics: function statics(Constructor, _statics) {
      mixStaticSpecIntoComponent(Constructor, _statics);
    },
    autobind: function autobind() {}
  };

  function validateMethodOverride(isAlreadyDefined, name) {
    var specPolicy = ReactClassInterface.hasOwnProperty(name)
      ? ReactClassInterface[name]
      : null;

    // Disallow overriding of base class methods unless explicitly allowed.
    if (ReactClassMixin.hasOwnProperty(name)) {
      _invariant(
        specPolicy === "OVERRIDE_BASE",
        "ReactClassInterface: You are attempting to override " +
          "`%s` from your class specification. Ensure that your method names " +
          "do not overlap with React methods.",
        name
      );
    }

    // Disallow defining methods more than once unless explicitly allowed.
    if (isAlreadyDefined) {
      _invariant(
        specPolicy === "DEFINE_MANY" || specPolicy === "DEFINE_MANY_MERGED",
        "ReactClassInterface: You are attempting to define " +
          "`%s` on your component more than once. This conflict may be due " +
          "to a mixin.",
        name
      );
    }
  }

  /**
 * Mixin helper which handles policy validation and reserved
 * specification keys when building React classes.
 */
  function mixSpecIntoComponent(Constructor, spec) {
    if (!spec) {
      return;
    }

    _invariant(
      typeof spec !== "function",
      "ReactClass: You're attempting to " +
        "use a component class or function as a mixin. Instead, just use a " +
        "regular object."
    );

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

    // By handling mixins before any other properties, we ensure the same
    // chaining order is applied to methods with DEFINE_MANY policy, whether
    // mixins are listed before or after these methods in the spec.
    if (spec.hasOwnProperty(MIXINS_KEY)) {
      RESERVED_SPEC_KEYS.mixins(Constructor, spec.mixins);
    }

    for (var name in spec) {
      if (!spec.hasOwnProperty(name)) {
        continue;
      }

      if (name === MIXINS_KEY) {
        // We have already handled mixins in a special case above.
        continue;
      }

      var property = spec[name];
      var isAlreadyDefined = proto.hasOwnProperty(name);
      validateMethodOverride(isAlreadyDefined, name);
      if (RESERVED_SPEC_KEYS.hasOwnProperty(name)) {
        RESERVED_SPEC_KEYS[name](Constructor, property);
      } else {
        // Setup methods on prototype:
        // The following member methods should not be automatically bound:
        // 1. Expected ReactClass methods (in the "interface").
        // 2. Overridden methods (that were mixed in).
        var isReactClassMethod = ReactClassInterface.hasOwnProperty(name);
        var isFunction = typeof property === "function";
        var shouldAutoBind =
          isFunction &&
          !isReactClassMethod &&
          !isAlreadyDefined &&
          spec.autobind !== false;

        if (shouldAutoBind) {
          autoBindPairs.push(name, property);
          proto[name] = property;
        } else {
          if (isAlreadyDefined) {
            var specPolicy = ReactClassInterface[name];

            // These cases should already be caught by validateMethodOverride.
            _invariant(
              isReactClassMethod &&
                (specPolicy === "DEFINE_MANY_MERGED" ||
                  specPolicy === "DEFINE_MANY"),
              "ReactClass: Unexpected spec policy %s for key %s " +
                "when mixing in component specs.",
              specPolicy,
              name
            );

            // For methods which are defined more than once, call the existing
            // methods before calling the new property, merging if appropriate.
            if (specPolicy === "DEFINE_MANY_MERGED") {
              console.log(name, "createMergedResultFunction");

              proto[name] = createMergedResultFunction(proto[name], property);
            } else if (specPolicy === "DEFINE_MANY") {
              proto[name] = createChainedFunction(proto[name], property);
            }
          } else {
            proto[name] = property;
            {
              // Add verbose displayName to the function, which helps when looking
              // at profiling tools.
              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;
      }

      var isReserved = name in RESERVED_SPEC_KEYS;
      _invariant(
        !isReserved,
        "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
      );

      var isInherited = name in Constructor;
      _invariant(
        !isInherited,
        "ReactClass: You are attempting to define " +
          "`%s` on your component more than once. This conflict may be " +
          "due to a mixin.",
        name
      );
      Constructor[name] = property;
    }
  }

  function mergeIntoWithNoDuplicateKeys(one, two) {
    _invariant(
      one &&
        two &&
        (typeof one === "undefined" ? "undefined" : _typeof(one)) ===
          "object" &&
        (typeof two === "undefined" ? "undefined" : _typeof(two)) === "object",
      "mergeIntoWithNoDuplicateKeys(): Cannot merge non-objects."
    );

    for (var key in two) {
      if (two.hasOwnProperty(key)) {
        one[key] = two[key];
      }
    }
    return one;
  }

  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(c, a);
      mergeIntoWithNoDuplicateKeys(c, b);
      return c;
    };
  }

  function createChainedFunction(one, two) {
    return function chainedFunction() {
      one.apply(this, arguments);
      two.apply(this, arguments);
    };
  }

  function bindAutoBindMethod(component, method) {
    var boundMethod = method.bind(component);
    return boundMethod;
  }

  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 = {
    /**
   * TODO: This will be deprecated because state should always keep a consistent
   * type signature and the only use case for this, is to avoid that.
   */
    replaceState: function replaceState(newState, callback) {
      // this.updater.enqueueReplaceState(this, newState, callback);
    },

    /**
   * Checks whether or not this composite component is mounted.
   * @return {boolean} True if mounted, false otherwise.
   * @protected
   * @final
   */
    isMounted: function isMounted() {
      return !!this._rendered && this._rendered._hostNode;
    }
  };

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

  /**
 * 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
 */
  function newCtor(className, ReactComponent, bindAutoBindMethods) {
    var curry = Function(
      "ReactComponent",
      "bindAutoBindMethods",
      `return function ${className}(props, context) {
    ReactComponent.call(this, props, context);
    var initialState = this.getInitialState ? this.getInitialState() : {};

    this.state = initialState;
    // This constructor gets overridden by mocks. The argument is used
    // by mocks to assert on what gets mounted.

    // Wire up auto-binding
    if (this.__reactAutoBindPairs.length) {
      bindAutoBindMethods(this);
    }
  };`
    );
    return curry(ReactComponent, bindAutoBindMethods);
  }

  function createClass(spec) {
    // To keep our warnings more understandable, we'll use a little hack here to
    // ensure that Constructor.name !== 'Constructor'. This makes sure we don't
    // unnecessarily identify a class without displayName as 'Constructor'.
    var Constructor = newCtor(
      spec.displayName || "Component",
      ReactComponent,
      bindAutoBindMethods
    );

    Constructor.prototype = new ReactClassComponent();
    Constructor.prototype.constructor = Constructor;
    Constructor.prototype.__reactAutoBindPairs = [];

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

    // mixSpecIntoComponent(Constructor, IsMountedPreMixin);
    mixSpecIntoComponent(Constructor, spec);
    // mixSpecIntoComponent(Constructor, IsMountedPostMixin);

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

    _invariant(
      Constructor.prototype.render,
      "createClass(...): Class specification must implement a `render` method."
    );

    // 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;
  }
  React.createClass = createClass;
@RubyLouvre
Copy link
Owner Author

第二代

import { extend, isFn, inherit, noop } from "./util";
import { Component } from "./Component";
/**
 * 为了兼容0.13之前的版本
 */
var MANY = "DEFINE_MANY";
var MANY_MERGED = "MANY_MERGED";
var ReactClassInterface = {
  mixins: MANY,
  statics: MANY,
  propTypes: MANY,
  contextTypes: MANY,
  childContextTypes: MANY,
  getDefaultProps: MANY_MERGED,
  getInitialState: MANY_MERGED,
  getChildContext: MANY_MERGED,
  render: "ONCE",
  componentWillMount: MANY,
  componentDidMount: MANY,
  componentWillReceiveProps: MANY,
  shouldComponentUpdate: "DEFINE_ONCE",
  componentWillUpdate: MANY,
  componentDidUpdate: MANY,
  componentWillUnmount: MANY
};

var specHandle = {
  displayName(Ctor, value, name) {
    Ctor[name] = value;
  },
  mixins(Ctor, value) {
    if (value) {
      for (var i = 0; i < value.length; i++) {
        mixSpecIntoComponent(Ctor, value[i]);
      }
    }
  },
  propTypes: mergeObject,
  childContextTypes: mergeObject,
  contextTypes: mergeObject,

  getDefaultProps(Ctor, value, name) {
    if (Ctor[name]) {
      Ctor[name] = createMergedResultFunction(Ctor[name], value);
    } else {
      Ctor[name] = value;
    }
  },

  statics(Ctor, value) {
    extend(Ctor, Object(value));
  },
  autobind: noop
};

function mergeObject(fn, value, name) {
  fn[name] = Object.assign({}, fn[name], value);
}

//防止覆盖Component内部一些重要的方法或属性
var protectedProps = {
  mixin: 1,
  setState: 1,
  forceUpdate: 1,
  __processPendingState: 1,
  __pendingCallbacks: 1,
  __pendingStates: 1
};

function mixSpecIntoComponent(Ctor, spec) {
  if (!spec) {
    return;
  }
  if (isFn(spec)) {
    console.warn("createClass(spec)中的spec不能为函数,只能是纯对象"); // eslint-disable-line
  }

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

  if (spec.hasOwnProperty("mixin")) {
    specHandle.mixins(Ctor, spec.mixins);
  }

  for (var name in spec) {
    if (!spec.hasOwnProperty(name)) {
      continue;
    }
    if (protectedProps[name] === 1) {
      continue;
    }

    var property = spec[name];
    var isAlreadyDefined = proto.hasOwnProperty(name);

    if (specHandle.hasOwnProperty(name)) {
      specHandle[name](Ctor, property, name);
    } else {
      var isReactClassMethod = ReactClassInterface.hasOwnProperty(name);
      var shouldAutoBind =
        isFn(property) &&
        !isReactClassMethod &&
        !isAlreadyDefined &&
        spec.autobind !== false;

      if (shouldAutoBind) {
        autoBindPairs.push(name, property);
        proto[name] = property;
      } else {
        if (isAlreadyDefined) {
          var specPolicy = ReactClassInterface[name];
          //合并多个同名函数
          if (specPolicy === MANY_MERGED) {
            //这个是有返回值
            proto[name] = createMergedResultFunction(proto[name], property);
          } else if (specPolicy === MANY) {
            //这个没有返回值
            proto[name] = createChainedFunction(proto[name], property);
          }
        } else {
          proto[name] = property;
        }
      }
    }
  }
}

function mergeOwnProperties(one, two) {
  for (var key in two) {
    if (two.hasOwnProperty(key)) {
      one[key] = two[key];
    }
  }
  return one;
}

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 = {};
    mergeOwnProperties(c, a);
    mergeOwnProperties(c, b);
    return c;
  };
}

function createChainedFunction(one, two) {
  return function chainedFunction() {
    one.apply(this, arguments);
    two.apply(this, arguments);
  };
}

function bindAutoBindMethod(component, method) {
  var boundMethod = method.bind(component);
  return boundMethod;
}

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);
  }
}

//创建一个构造器
function newCtor(className) {
  var curry = Function(
    "ReactComponent",
    "bindAutoBindMethods",
    `return function ${className}(props, context) {
    ReactComponent.call(this, props, context);
    this.state = this.getInitialState ? this.getInitialState() : {};
    if (this.__reactAutoBindPairs.length) {
      bindAutoBindMethods(this);
    }
  };`
  );
  return curry(Component, bindAutoBindMethods);
}
var warnOnce = 1;
export function createClass(spec) {
  if (warnOnce) {
    warnOnce = 0;
    console.warn("createClass已经过时,强烈建议使用es6方式定义类"); // eslint-disable-line
  }
  var Constructor = newCtor(spec.displayName || "Component");
  var proto = inherit(Constructor, Component);
  proto.__reactAutoBindPairs = [];
  delete proto.render;

  mixSpecIntoComponent(Constructor, spec);
  if (isFn(Constructor.getDefaultProps)) {
    Constructor.defaultProps = Constructor.getDefaultProps();
  }

  //性能优化,为了防止在原型链进行无用的查找,直接将用户没有定义的生命周期钩子置为null
  for (var methodName in ReactClassInterface) {
    if (!proto[methodName]) {
      proto[methodName] = null;
    }
  }

  return Constructor;
}

@RubyLouvre
Copy link
Owner Author

第三代

import {extend, isFn, inherit} from "./util";
import {Component} from "./Component";
/**
 * 为了兼容0.13之前的版本
 */
const LiFECYCLE = {
    render: 1,
    shouldComponentUpdate: 1,
    componentWillReceiveProps: 1,
    componentWillUpdate: 1,
    componentDidUpdate: 1,
    componentWillMount: 1,
    componentDidMount: 1,
    componentWillUnmount: 1,
    componentDidUnmount: 1
};

function collectMixins(mixins) {
    let keyed = {};

    for (let i = 0; i < mixins.length; i++) {
        let mixin = mixins[i];
        if (mixin.mixins) {
            applyMixins(mixin, collectMixins(mixin.mixins));
        }

        for (let key in mixin) {
            if (mixin.hasOwnProperty(key) && key !== 'mixins') {
                (keyed[key] || (keyed[key] = [])).push(mixin[key]);
            }
        }
    }

    return keyed;
}
var MANY_MERGED = {
    getInitialState: 1,
    getDefaultProps: 1,
    getChildContext: 1
}
function flattenHooks(key, hooks) {
    let hookType = typeof hooks[0];
    if (hookType === 'object') {
        // Merge objects
        hooks.unshift({});
        return Object
            .assign
            .apply(null, hooks);
    } else if (hookType === 'function') {
        return function () {
            let ret;
            for (let i = 0; i < hooks.length; i++) {
                let r = hooks[i].apply(this, arguments);
                if (r && MANY_MERGED[key]) {
                    if (!ret) 
                        ret = {};
                    Object.assign(ret, r);
                }
            }
            return ret;
        };
    } else {
        return hooks[0];
    }
}

function applyMixins(proto, mixins) {
    for (let key in mixins) {
        if (mixins.hasOwnProperty(key)) {
            proto[key] = flattenHooks(key, mixins[key].concat(proto[key] || []));
        }
    }
}

//创建一个构造器
function newCtor(className) {
    var curry = Function("ReactComponent", "blacklist", 
    `return function ${className}(props, context) {
      ReactComponent.call(this, props, context);

     for (let methodName in this) {
        let method = this[methodName];
        if (typeof method  === 'function'&& !blacklist[methodName]) {
          this[methodName] = method.bind(this);
        }
      }

      if (spec.getInitialState) {
        this.state = spec.getInitialState.call(this);
      }

  };`);
    return curry(Component, LiFECYCLE);
}

var warnOnce = 1;
export function createClass(spec) {
    if (warnOnce) {
        warnOnce = 0;
        console.warn("createClass已经过时,强烈建议使用es6方式定义类"); // eslint-disable-line
    }
    var Constructor = newCtor(spec.displayName || "Component");
    var proto = inherit(Constructor, Component);
    //如果mixins里面非常复杂,可能mixin还包含其他mixin
    if (spec.mixins) {
        applyMixins(spec, collectMixins(spec.mixins));
    }

    extend(proto, spec);

    if (spec.statics) {
       extend(Constructor, spec.statics);
    }
    "propTypes,contextTypes,childContextTypes,displayName"
        .replace(/\w+/g, function (name) {
            if (spec[name]) {
                Constructor[name] = spec[name];
            }
        })

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

    return Constructor;
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant