Move Class Creation Concerns to ReactClass
This moves logic responsible for class creation and mixins into the ReactClass module. This currently creates a class that extends the ReactCompositeBase but in the future, this will be decoupled as ReactCompositeComponent will compose these classes. This is just a cut/paste.
This commit is contained in:
parent
c7bb936566
commit
d9fe40e147
|
@ -11,6 +11,690 @@
|
|||
|
||||
"use strict";
|
||||
|
||||
var ReactClass = require('ReactCompositeComponent');
|
||||
var ReactCompositeComponent = require('ReactCompositeComponent');
|
||||
var ReactElement = require('ReactElement');
|
||||
var ReactElementValidator = require('ReactElementValidator');
|
||||
var ReactLegacyElement = require('ReactLegacyElement');
|
||||
var ReactPropTypeLocations = require('ReactPropTypeLocations');
|
||||
var ReactPropTypeLocationNames = require('ReactPropTypeLocationNames');
|
||||
|
||||
var assign = require('Object.assign');
|
||||
var invariant = require('invariant');
|
||||
var keyMirror = require('keyMirror');
|
||||
var keyOf = require('keyOf');
|
||||
var monitorCodeUse = require('monitorCodeUse');
|
||||
var mapObject = require('mapObject');
|
||||
|
||||
var MIXINS_KEY = keyOf({mixins: null});
|
||||
|
||||
/**
|
||||
* Policies that describe methods in `ReactClassInterface`.
|
||||
*/
|
||||
var SpecPolicy = keyMirror({
|
||||
/**
|
||||
* These methods may be defined only once by the class specification or mixin.
|
||||
*/
|
||||
DEFINE_ONCE: null,
|
||||
/**
|
||||
* These methods may be defined by both the class specification and mixins.
|
||||
* Subsequent definitions will be chained. These methods must return void.
|
||||
*/
|
||||
DEFINE_MANY: null,
|
||||
/**
|
||||
* These methods are overriding the base class.
|
||||
*/
|
||||
OVERRIDE_BASE: null,
|
||||
/**
|
||||
* These methods are similar to DEFINE_MANY, except we assume they return
|
||||
* objects. We try to merge the keys of the return values of all the mixed in
|
||||
* functions. If there is a key conflict we throw.
|
||||
*/
|
||||
DEFINE_MANY_MERGED: null
|
||||
});
|
||||
|
||||
|
||||
var injectedMixins = [];
|
||||
|
||||
/**
|
||||
* Composite components are higher-level components that compose other composite
|
||||
* or native components.
|
||||
*
|
||||
* To create a new type of `ReactClass`, pass a specification of
|
||||
* your new class to `React.createClass`. The only requirement of your class
|
||||
* specification is that you implement a `render` method.
|
||||
*
|
||||
* var MyComponent = React.createClass({
|
||||
* render: function() {
|
||||
* return <div>Hello World</div>;
|
||||
* }
|
||||
* });
|
||||
*
|
||||
* The class specification supports a specific protocol of methods that have
|
||||
* special meaning (e.g. `render`). See `ReactClassInterface` for
|
||||
* more the comprehensive protocol. Any other properties and methods in the
|
||||
* class specification will available on the prototype.
|
||||
*
|
||||
* @interface ReactClassInterface
|
||||
* @internal
|
||||
*/
|
||||
var ReactClassInterface = {
|
||||
|
||||
/**
|
||||
* An array of Mixin objects to include when defining your component.
|
||||
*
|
||||
* @type {array}
|
||||
* @optional
|
||||
*/
|
||||
mixins: SpecPolicy.DEFINE_MANY,
|
||||
|
||||
/**
|
||||
* An object containing properties and methods that should be defined on
|
||||
* the component's constructor instead of its prototype (static methods).
|
||||
*
|
||||
* @type {object}
|
||||
* @optional
|
||||
*/
|
||||
statics: SpecPolicy.DEFINE_MANY,
|
||||
|
||||
/**
|
||||
* Definition of prop types for this component.
|
||||
*
|
||||
* @type {object}
|
||||
* @optional
|
||||
*/
|
||||
propTypes: SpecPolicy.DEFINE_MANY,
|
||||
|
||||
/**
|
||||
* Definition of context types for this component.
|
||||
*
|
||||
* @type {object}
|
||||
* @optional
|
||||
*/
|
||||
contextTypes: SpecPolicy.DEFINE_MANY,
|
||||
|
||||
/**
|
||||
* Definition of context types this component sets for its children.
|
||||
*
|
||||
* @type {object}
|
||||
* @optional
|
||||
*/
|
||||
childContextTypes: SpecPolicy.DEFINE_MANY,
|
||||
|
||||
// ==== Definition methods ====
|
||||
|
||||
/**
|
||||
* Invoked when the component is mounted. Values in the mapping will be set on
|
||||
* `this.props` if that prop is not specified (i.e. using an `in` check).
|
||||
*
|
||||
* This method is invoked before `getInitialState` and therefore cannot rely
|
||||
* on `this.state` or use `this.setState`.
|
||||
*
|
||||
* @return {object}
|
||||
* @optional
|
||||
*/
|
||||
getDefaultProps: SpecPolicy.DEFINE_MANY_MERGED,
|
||||
|
||||
/**
|
||||
* Invoked once before the component is mounted. The return value will be used
|
||||
* as the initial value of `this.state`.
|
||||
*
|
||||
* getInitialState: function() {
|
||||
* return {
|
||||
* isOn: false,
|
||||
* fooBaz: new BazFoo()
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* @return {object}
|
||||
* @optional
|
||||
*/
|
||||
getInitialState: SpecPolicy.DEFINE_MANY_MERGED,
|
||||
|
||||
/**
|
||||
* @return {object}
|
||||
* @optional
|
||||
*/
|
||||
getChildContext: SpecPolicy.DEFINE_MANY_MERGED,
|
||||
|
||||
/**
|
||||
* Uses props from `this.props` and state from `this.state` to render the
|
||||
* structure of the component.
|
||||
*
|
||||
* No guarantees are made about when or how often this method is invoked, so
|
||||
* it must not have side effects.
|
||||
*
|
||||
* render: function() {
|
||||
* var name = this.props.name;
|
||||
* return <div>Hello, {name}!</div>;
|
||||
* }
|
||||
*
|
||||
* @return {ReactComponent}
|
||||
* @nosideeffects
|
||||
* @required
|
||||
*/
|
||||
render: SpecPolicy.DEFINE_ONCE,
|
||||
|
||||
|
||||
|
||||
// ==== Delegate methods ====
|
||||
|
||||
/**
|
||||
* Invoked when the component is initially created and about to be mounted.
|
||||
* This may have side effects, but any external subscriptions or data created
|
||||
* by this method must be cleaned up in `componentWillUnmount`.
|
||||
*
|
||||
* @optional
|
||||
*/
|
||||
componentWillMount: SpecPolicy.DEFINE_MANY,
|
||||
|
||||
/**
|
||||
* Invoked when the component has been mounted and has a DOM representation.
|
||||
* However, there is no guarantee that the DOM node is in the document.
|
||||
*
|
||||
* Use this as an opportunity to operate on the DOM when the component has
|
||||
* been mounted (initialized and rendered) for the first time.
|
||||
*
|
||||
* @param {DOMElement} rootNode DOM element representing the component.
|
||||
* @optional
|
||||
*/
|
||||
componentDidMount: SpecPolicy.DEFINE_MANY,
|
||||
|
||||
/**
|
||||
* Invoked before the component receives new props.
|
||||
*
|
||||
* Use this as an opportunity to react to a prop transition by updating the
|
||||
* state using `this.setState`. Current props are accessed via `this.props`.
|
||||
*
|
||||
* componentWillReceiveProps: function(nextProps, nextContext) {
|
||||
* this.setState({
|
||||
* likesIncreasing: nextProps.likeCount > this.props.likeCount
|
||||
* });
|
||||
* }
|
||||
*
|
||||
* NOTE: There is no equivalent `componentWillReceiveState`. An incoming prop
|
||||
* transition may cause a state change, but the opposite is not true. If you
|
||||
* need it, you are probably looking for `componentWillUpdate`.
|
||||
*
|
||||
* @param {object} nextProps
|
||||
* @optional
|
||||
*/
|
||||
componentWillReceiveProps: SpecPolicy.DEFINE_MANY,
|
||||
|
||||
/**
|
||||
* Invoked while deciding if the component should be updated as a result of
|
||||
* receiving new props, state and/or context.
|
||||
*
|
||||
* Use this as an opportunity to `return false` when you're certain that the
|
||||
* transition to the new props/state/context will not require a component
|
||||
* update.
|
||||
*
|
||||
* shouldComponentUpdate: function(nextProps, nextState, nextContext) {
|
||||
* return !equal(nextProps, this.props) ||
|
||||
* !equal(nextState, this.state) ||
|
||||
* !equal(nextContext, this.context);
|
||||
* }
|
||||
*
|
||||
* @param {object} nextProps
|
||||
* @param {?object} nextState
|
||||
* @param {?object} nextContext
|
||||
* @return {boolean} True if the component should update.
|
||||
* @optional
|
||||
*/
|
||||
shouldComponentUpdate: SpecPolicy.DEFINE_ONCE,
|
||||
|
||||
/**
|
||||
* Invoked when the component is about to update due to a transition from
|
||||
* `this.props`, `this.state` and `this.context` to `nextProps`, `nextState`
|
||||
* and `nextContext`.
|
||||
*
|
||||
* Use this as an opportunity to perform preparation before an update occurs.
|
||||
*
|
||||
* NOTE: You **cannot** use `this.setState()` in this method.
|
||||
*
|
||||
* @param {object} nextProps
|
||||
* @param {?object} nextState
|
||||
* @param {?object} nextContext
|
||||
* @param {ReactReconcileTransaction} transaction
|
||||
* @optional
|
||||
*/
|
||||
componentWillUpdate: SpecPolicy.DEFINE_MANY,
|
||||
|
||||
/**
|
||||
* Invoked when the component's DOM representation has been updated.
|
||||
*
|
||||
* Use this as an opportunity to operate on the DOM when the component has
|
||||
* been updated.
|
||||
*
|
||||
* @param {object} prevProps
|
||||
* @param {?object} prevState
|
||||
* @param {?object} prevContext
|
||||
* @param {DOMElement} rootNode DOM element representing the component.
|
||||
* @optional
|
||||
*/
|
||||
componentDidUpdate: SpecPolicy.DEFINE_MANY,
|
||||
|
||||
/**
|
||||
* Invoked when the component is about to be removed from its parent and have
|
||||
* its DOM representation destroyed.
|
||||
*
|
||||
* Use this as an opportunity to deallocate any external resources.
|
||||
*
|
||||
* NOTE: There is no `componentDidUnmount` since your component will have been
|
||||
* destroyed by that point.
|
||||
*
|
||||
* @optional
|
||||
*/
|
||||
componentWillUnmount: SpecPolicy.DEFINE_MANY,
|
||||
|
||||
|
||||
|
||||
// ==== Advanced methods ====
|
||||
|
||||
/**
|
||||
* Updates the component's currently mounted DOM representation.
|
||||
*
|
||||
* By default, this implements React's rendering and reconciliation algorithm.
|
||||
* Sophisticated clients may wish to override this.
|
||||
*
|
||||
* @param {ReactReconcileTransaction} transaction
|
||||
* @internal
|
||||
* @overridable
|
||||
*/
|
||||
updateComponent: SpecPolicy.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(Constructor, displayName) {
|
||||
Constructor.displayName = displayName;
|
||||
},
|
||||
mixins: function(Constructor, mixins) {
|
||||
if (mixins) {
|
||||
for (var i = 0; i < mixins.length; i++) {
|
||||
mixSpecIntoComponent(Constructor, mixins[i]);
|
||||
}
|
||||
}
|
||||
},
|
||||
childContextTypes: function(Constructor, childContextTypes) {
|
||||
validateTypeDef(
|
||||
Constructor,
|
||||
childContextTypes,
|
||||
ReactPropTypeLocations.childContext
|
||||
);
|
||||
Constructor.childContextTypes = assign(
|
||||
{},
|
||||
Constructor.childContextTypes,
|
||||
childContextTypes
|
||||
);
|
||||
},
|
||||
contextTypes: function(Constructor, contextTypes) {
|
||||
validateTypeDef(
|
||||
Constructor,
|
||||
contextTypes,
|
||||
ReactPropTypeLocations.context
|
||||
);
|
||||
Constructor.contextTypes = assign(
|
||||
{},
|
||||
Constructor.contextTypes,
|
||||
contextTypes
|
||||
);
|
||||
},
|
||||
/**
|
||||
* Special case getDefaultProps which should move into statics but requires
|
||||
* automatic merging.
|
||||
*/
|
||||
getDefaultProps: function(Constructor, getDefaultProps) {
|
||||
if (Constructor.getDefaultProps) {
|
||||
Constructor.getDefaultProps = createMergedResultFunction(
|
||||
Constructor.getDefaultProps,
|
||||
getDefaultProps
|
||||
);
|
||||
} else {
|
||||
Constructor.getDefaultProps = getDefaultProps;
|
||||
}
|
||||
},
|
||||
propTypes: function(Constructor, propTypes) {
|
||||
validateTypeDef(
|
||||
Constructor,
|
||||
propTypes,
|
||||
ReactPropTypeLocations.prop
|
||||
);
|
||||
Constructor.propTypes = assign(
|
||||
{},
|
||||
Constructor.propTypes,
|
||||
propTypes
|
||||
);
|
||||
},
|
||||
statics: function(Constructor, statics) {
|
||||
mixStaticSpecIntoComponent(Constructor, statics);
|
||||
}
|
||||
};
|
||||
|
||||
function validateTypeDef(Constructor, typeDef, location) {
|
||||
for (var propName in typeDef) {
|
||||
if (typeDef.hasOwnProperty(propName)) {
|
||||
invariant(
|
||||
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
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function validateMethodOverride(proto, name) {
|
||||
var specPolicy = ReactClassInterface.hasOwnProperty(name) ?
|
||||
ReactClassInterface[name] :
|
||||
null;
|
||||
|
||||
// Disallow overriding of base class methods unless explicitly allowed.
|
||||
if (ReactCompositeComponent.Base.prototype.hasOwnProperty(name)) {
|
||||
invariant(
|
||||
specPolicy === 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 (proto.hasOwnProperty(name)) {
|
||||
invariant(
|
||||
specPolicy === SpecPolicy.DEFINE_MANY ||
|
||||
specPolicy === 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 classses.
|
||||
*/
|
||||
function mixSpecIntoComponent(Constructor, spec) {
|
||||
if (!spec) {
|
||||
return;
|
||||
}
|
||||
|
||||
invariant(
|
||||
typeof spec !== 'function',
|
||||
'ReactClass: You\'re attempting to ' +
|
||||
'use a component class as a mixin. Instead, just use a regular object.'
|
||||
);
|
||||
invariant(
|
||||
!ReactElement.isValidElement(spec),
|
||||
'ReactClass: You\'re attempting to ' +
|
||||
'use a component as a mixin. Instead, just use a regular object.'
|
||||
);
|
||||
|
||||
var proto = Constructor.prototype;
|
||||
|
||||
// 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];
|
||||
validateMethodOverride(proto, 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 isAlreadyDefined = proto.hasOwnProperty(name);
|
||||
var markedDontBind = property && property.__reactDontBind;
|
||||
var isFunction = typeof property === 'function';
|
||||
var shouldAutoBind =
|
||||
isFunction &&
|
||||
!isReactClassMethod &&
|
||||
!isAlreadyDefined &&
|
||||
!markedDontBind;
|
||||
|
||||
if (shouldAutoBind) {
|
||||
if (!proto.__reactAutoBindMap) {
|
||||
proto.__reactAutoBindMap = {};
|
||||
}
|
||||
proto.__reactAutoBindMap[name] = property;
|
||||
proto[name] = property;
|
||||
} else {
|
||||
if (isAlreadyDefined) {
|
||||
var specPolicy = ReactClassInterface[name];
|
||||
|
||||
// These cases should already be caught by validateMethodOverride
|
||||
invariant(
|
||||
isReactClassMethod && (
|
||||
specPolicy === SpecPolicy.DEFINE_MANY_MERGED ||
|
||||
specPolicy === 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 === SpecPolicy.DEFINE_MANY_MERGED) {
|
||||
proto[name] = createMergedResultFunction(proto[name], property);
|
||||
} else if (specPolicy === SpecPolicy.DEFINE_MANY) {
|
||||
proto[name] = createChainedFunction(proto[name], property);
|
||||
}
|
||||
} else {
|
||||
proto[name] = property;
|
||||
if (__DEV__) {
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge two objects, but throw if both contain the same key.
|
||||
*
|
||||
* @param {object} one The first object, which is mutated.
|
||||
* @param {object} two The second object
|
||||
* @return {object} one after it has been mutated to contain everything in two.
|
||||
*/
|
||||
function mergeObjectsWithNoDuplicateKeys(one, two) {
|
||||
invariant(
|
||||
one && two && typeof one === 'object' && typeof two === 'object',
|
||||
'mergeObjectsWithNoDuplicateKeys(): Cannot merge non-objects'
|
||||
);
|
||||
|
||||
mapObject(two, function(value, key) {
|
||||
invariant(
|
||||
one[key] === undefined,
|
||||
'mergeObjectsWithNoDuplicateKeys(): ' +
|
||||
'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
|
||||
);
|
||||
one[key] = value;
|
||||
});
|
||||
return one;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a function that invokes two functions and merges their return values.
|
||||
*
|
||||
* @param {function} one Function to invoke first.
|
||||
* @param {function} two Function to invoke second.
|
||||
* @return {function} Function that invokes the two argument functions.
|
||||
* @private
|
||||
*/
|
||||
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;
|
||||
}
|
||||
return mergeObjectsWithNoDuplicateKeys(a, b);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a function that invokes two functions and ignores their return vales.
|
||||
*
|
||||
* @param {function} one Function to invoke first.
|
||||
* @param {function} two Function to invoke second.
|
||||
* @return {function} Function that invokes the two argument functions.
|
||||
* @private
|
||||
*/
|
||||
function createChainedFunction(one, two) {
|
||||
return function chainedFunction() {
|
||||
one.apply(this, arguments);
|
||||
two.apply(this, arguments);
|
||||
};
|
||||
}
|
||||
|
||||
var ReactClass = {
|
||||
|
||||
/**
|
||||
* Creates a composite component class given a class specification.
|
||||
*
|
||||
* @param {object} spec Class specification (which must define `render`).
|
||||
* @return {function} Component constructor function.
|
||||
* @public
|
||||
*/
|
||||
createClass: function(spec) {
|
||||
var Constructor = function(props) {
|
||||
// This constructor is overridden by mocks. The argument is used
|
||||
// by mocks to assert on what gets mounted. This will later be used
|
||||
// by the stand-alone class implementation.
|
||||
};
|
||||
Constructor.prototype = new ReactCompositeComponent.Base();
|
||||
Constructor.prototype.constructor = Constructor;
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
invariant(
|
||||
Constructor.prototype.render,
|
||||
'createClass(...): Class specification must implement a `render` method.'
|
||||
);
|
||||
|
||||
if (__DEV__) {
|
||||
if (Constructor.prototype.componentShouldUpdate) {
|
||||
monitorCodeUse(
|
||||
'react_component_should_update_warning',
|
||||
{ component: spec.displayName }
|
||||
);
|
||||
console.warn(
|
||||
(spec.displayName || 'A component') + ' 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.'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Reduce time spent doing lookups by setting these on the prototype.
|
||||
for (var methodName in ReactClassInterface) {
|
||||
if (!Constructor.prototype[methodName]) {
|
||||
Constructor.prototype[methodName] = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (__DEV__) {
|
||||
return ReactLegacyElement.wrapFactory(
|
||||
ReactElementValidator.createFactory(Constructor)
|
||||
);
|
||||
}
|
||||
return ReactLegacyElement.wrapFactory(
|
||||
ReactElement.createFactory(Constructor)
|
||||
);
|
||||
},
|
||||
|
||||
injection: {
|
||||
injectMixin: function(mixin) {
|
||||
injectedMixins.push(mixin);
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
module.exports = ReactClass;
|
||||
|
|
|
@ -15,381 +15,22 @@ var ReactComponent = require('ReactComponent');
|
|||
var ReactContext = require('ReactContext');
|
||||
var ReactCurrentOwner = require('ReactCurrentOwner');
|
||||
var ReactElement = require('ReactElement');
|
||||
var ReactElementValidator = require('ReactElementValidator');
|
||||
var ReactEmptyComponent = require('ReactEmptyComponent');
|
||||
var ReactErrorUtils = require('ReactErrorUtils');
|
||||
var ReactLegacyElement = require('ReactLegacyElement');
|
||||
var ReactOwner = require('ReactOwner');
|
||||
var ReactPerf = require('ReactPerf');
|
||||
var ReactPropTransferer = require('ReactPropTransferer');
|
||||
var ReactPropTypeLocations = require('ReactPropTypeLocations');
|
||||
var ReactPropTypeLocationNames = require('ReactPropTypeLocationNames');
|
||||
var ReactUpdates = require('ReactUpdates');
|
||||
|
||||
var assign = require('Object.assign');
|
||||
var instantiateReactComponent = require('instantiateReactComponent');
|
||||
var invariant = require('invariant');
|
||||
var keyMirror = require('keyMirror');
|
||||
var keyOf = require('keyOf');
|
||||
var monitorCodeUse = require('monitorCodeUse');
|
||||
var mapObject = require('mapObject');
|
||||
var shouldUpdateReactComponent = require('shouldUpdateReactComponent');
|
||||
var warning = require('warning');
|
||||
|
||||
var MIXINS_KEY = keyOf({mixins: null});
|
||||
|
||||
/**
|
||||
* Policies that describe methods in `ReactCompositeComponentInterface`.
|
||||
*/
|
||||
var SpecPolicy = keyMirror({
|
||||
/**
|
||||
* These methods may be defined only once by the class specification or mixin.
|
||||
*/
|
||||
DEFINE_ONCE: null,
|
||||
/**
|
||||
* These methods may be defined by both the class specification and mixins.
|
||||
* Subsequent definitions will be chained. These methods must return void.
|
||||
*/
|
||||
DEFINE_MANY: null,
|
||||
/**
|
||||
* These methods are overriding the base ReactCompositeComponent class.
|
||||
*/
|
||||
OVERRIDE_BASE: null,
|
||||
/**
|
||||
* These methods are similar to DEFINE_MANY, except we assume they return
|
||||
* objects. We try to merge the keys of the return values of all the mixed in
|
||||
* functions. If there is a key conflict we throw.
|
||||
*/
|
||||
DEFINE_MANY_MERGED: null
|
||||
});
|
||||
|
||||
|
||||
var injectedMixins = [];
|
||||
|
||||
/**
|
||||
* Composite components are higher-level components that compose other composite
|
||||
* or native components.
|
||||
*
|
||||
* To create a new type of `ReactCompositeComponent`, pass a specification of
|
||||
* your new class to `React.createClass`. The only requirement of your class
|
||||
* specification is that you implement a `render` method.
|
||||
*
|
||||
* var MyComponent = React.createClass({
|
||||
* render: function() {
|
||||
* return <div>Hello World</div>;
|
||||
* }
|
||||
* });
|
||||
*
|
||||
* The class specification supports a specific protocol of methods that have
|
||||
* special meaning (e.g. `render`). See `ReactCompositeComponentInterface` for
|
||||
* more the comprehensive protocol. Any other properties and methods in the
|
||||
* class specification will available on the prototype.
|
||||
*
|
||||
* @interface ReactCompositeComponentInterface
|
||||
* @internal
|
||||
*/
|
||||
var ReactCompositeComponentInterface = {
|
||||
|
||||
/**
|
||||
* An array of Mixin objects to include when defining your component.
|
||||
*
|
||||
* @type {array}
|
||||
* @optional
|
||||
*/
|
||||
mixins: SpecPolicy.DEFINE_MANY,
|
||||
|
||||
/**
|
||||
* An object containing properties and methods that should be defined on
|
||||
* the component's constructor instead of its prototype (static methods).
|
||||
*
|
||||
* @type {object}
|
||||
* @optional
|
||||
*/
|
||||
statics: SpecPolicy.DEFINE_MANY,
|
||||
|
||||
/**
|
||||
* Definition of prop types for this component.
|
||||
*
|
||||
* @type {object}
|
||||
* @optional
|
||||
*/
|
||||
propTypes: SpecPolicy.DEFINE_MANY,
|
||||
|
||||
/**
|
||||
* Definition of context types for this component.
|
||||
*
|
||||
* @type {object}
|
||||
* @optional
|
||||
*/
|
||||
contextTypes: SpecPolicy.DEFINE_MANY,
|
||||
|
||||
/**
|
||||
* Definition of context types this component sets for its children.
|
||||
*
|
||||
* @type {object}
|
||||
* @optional
|
||||
*/
|
||||
childContextTypes: SpecPolicy.DEFINE_MANY,
|
||||
|
||||
// ==== Definition methods ====
|
||||
|
||||
/**
|
||||
* Invoked when the component is mounted. Values in the mapping will be set on
|
||||
* `this.props` if that prop is not specified (i.e. using an `in` check).
|
||||
*
|
||||
* This method is invoked before `getInitialState` and therefore cannot rely
|
||||
* on `this.state` or use `this.setState`.
|
||||
*
|
||||
* @return {object}
|
||||
* @optional
|
||||
*/
|
||||
getDefaultProps: SpecPolicy.DEFINE_MANY_MERGED,
|
||||
|
||||
/**
|
||||
* Invoked once before the component is mounted. The return value will be used
|
||||
* as the initial value of `this.state`.
|
||||
*
|
||||
* getInitialState: function() {
|
||||
* return {
|
||||
* isOn: false,
|
||||
* fooBaz: new BazFoo()
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* @return {object}
|
||||
* @optional
|
||||
*/
|
||||
getInitialState: SpecPolicy.DEFINE_MANY_MERGED,
|
||||
|
||||
/**
|
||||
* @return {object}
|
||||
* @optional
|
||||
*/
|
||||
getChildContext: SpecPolicy.DEFINE_MANY_MERGED,
|
||||
|
||||
/**
|
||||
* Uses props from `this.props` and state from `this.state` to render the
|
||||
* structure of the component.
|
||||
*
|
||||
* No guarantees are made about when or how often this method is invoked, so
|
||||
* it must not have side effects.
|
||||
*
|
||||
* render: function() {
|
||||
* var name = this.props.name;
|
||||
* return <div>Hello, {name}!</div>;
|
||||
* }
|
||||
*
|
||||
* @return {ReactComponent}
|
||||
* @nosideeffects
|
||||
* @required
|
||||
*/
|
||||
render: SpecPolicy.DEFINE_ONCE,
|
||||
|
||||
|
||||
|
||||
// ==== Delegate methods ====
|
||||
|
||||
/**
|
||||
* Invoked when the component is initially created and about to be mounted.
|
||||
* This may have side effects, but any external subscriptions or data created
|
||||
* by this method must be cleaned up in `componentWillUnmount`.
|
||||
*
|
||||
* @optional
|
||||
*/
|
||||
componentWillMount: SpecPolicy.DEFINE_MANY,
|
||||
|
||||
/**
|
||||
* Invoked when the component has been mounted and has a DOM representation.
|
||||
* However, there is no guarantee that the DOM node is in the document.
|
||||
*
|
||||
* Use this as an opportunity to operate on the DOM when the component has
|
||||
* been mounted (initialized and rendered) for the first time.
|
||||
*
|
||||
* @param {DOMElement} rootNode DOM element representing the component.
|
||||
* @optional
|
||||
*/
|
||||
componentDidMount: SpecPolicy.DEFINE_MANY,
|
||||
|
||||
/**
|
||||
* Invoked before the component receives new props.
|
||||
*
|
||||
* Use this as an opportunity to react to a prop transition by updating the
|
||||
* state using `this.setState`. Current props are accessed via `this.props`.
|
||||
*
|
||||
* componentWillReceiveProps: function(nextProps, nextContext) {
|
||||
* this.setState({
|
||||
* likesIncreasing: nextProps.likeCount > this.props.likeCount
|
||||
* });
|
||||
* }
|
||||
*
|
||||
* NOTE: There is no equivalent `componentWillReceiveState`. An incoming prop
|
||||
* transition may cause a state change, but the opposite is not true. If you
|
||||
* need it, you are probably looking for `componentWillUpdate`.
|
||||
*
|
||||
* @param {object} nextProps
|
||||
* @optional
|
||||
*/
|
||||
componentWillReceiveProps: SpecPolicy.DEFINE_MANY,
|
||||
|
||||
/**
|
||||
* Invoked while deciding if the component should be updated as a result of
|
||||
* receiving new props, state and/or context.
|
||||
*
|
||||
* Use this as an opportunity to `return false` when you're certain that the
|
||||
* transition to the new props/state/context will not require a component
|
||||
* update.
|
||||
*
|
||||
* shouldComponentUpdate: function(nextProps, nextState, nextContext) {
|
||||
* return !equal(nextProps, this.props) ||
|
||||
* !equal(nextState, this.state) ||
|
||||
* !equal(nextContext, this.context);
|
||||
* }
|
||||
*
|
||||
* @param {object} nextProps
|
||||
* @param {?object} nextState
|
||||
* @param {?object} nextContext
|
||||
* @return {boolean} True if the component should update.
|
||||
* @optional
|
||||
*/
|
||||
shouldComponentUpdate: SpecPolicy.DEFINE_ONCE,
|
||||
|
||||
/**
|
||||
* Invoked when the component is about to update due to a transition from
|
||||
* `this.props`, `this.state` and `this.context` to `nextProps`, `nextState`
|
||||
* and `nextContext`.
|
||||
*
|
||||
* Use this as an opportunity to perform preparation before an update occurs.
|
||||
*
|
||||
* NOTE: You **cannot** use `this.setState()` in this method.
|
||||
*
|
||||
* @param {object} nextProps
|
||||
* @param {?object} nextState
|
||||
* @param {?object} nextContext
|
||||
* @param {ReactReconcileTransaction} transaction
|
||||
* @optional
|
||||
*/
|
||||
componentWillUpdate: SpecPolicy.DEFINE_MANY,
|
||||
|
||||
/**
|
||||
* Invoked when the component's DOM representation has been updated.
|
||||
*
|
||||
* Use this as an opportunity to operate on the DOM when the component has
|
||||
* been updated.
|
||||
*
|
||||
* @param {object} prevProps
|
||||
* @param {?object} prevState
|
||||
* @param {?object} prevContext
|
||||
* @param {DOMElement} rootNode DOM element representing the component.
|
||||
* @optional
|
||||
*/
|
||||
componentDidUpdate: SpecPolicy.DEFINE_MANY,
|
||||
|
||||
/**
|
||||
* Invoked when the component is about to be removed from its parent and have
|
||||
* its DOM representation destroyed.
|
||||
*
|
||||
* Use this as an opportunity to deallocate any external resources.
|
||||
*
|
||||
* NOTE: There is no `componentDidUnmount` since your component will have been
|
||||
* destroyed by that point.
|
||||
*
|
||||
* @optional
|
||||
*/
|
||||
componentWillUnmount: SpecPolicy.DEFINE_MANY,
|
||||
|
||||
|
||||
|
||||
// ==== Advanced methods ====
|
||||
|
||||
/**
|
||||
* Updates the component's currently mounted DOM representation.
|
||||
*
|
||||
* By default, this implements React's rendering and reconciliation algorithm.
|
||||
* Sophisticated clients may wish to override this.
|
||||
*
|
||||
* @param {ReactReconcileTransaction} transaction
|
||||
* @internal
|
||||
* @overridable
|
||||
*/
|
||||
updateComponent: SpecPolicy.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(Constructor, displayName) {
|
||||
Constructor.displayName = displayName;
|
||||
},
|
||||
mixins: function(Constructor, mixins) {
|
||||
if (mixins) {
|
||||
for (var i = 0; i < mixins.length; i++) {
|
||||
mixSpecIntoComponent(Constructor, mixins[i]);
|
||||
}
|
||||
}
|
||||
},
|
||||
childContextTypes: function(Constructor, childContextTypes) {
|
||||
validateTypeDef(
|
||||
Constructor,
|
||||
childContextTypes,
|
||||
ReactPropTypeLocations.childContext
|
||||
);
|
||||
Constructor.childContextTypes = assign(
|
||||
{},
|
||||
Constructor.childContextTypes,
|
||||
childContextTypes
|
||||
);
|
||||
},
|
||||
contextTypes: function(Constructor, contextTypes) {
|
||||
validateTypeDef(
|
||||
Constructor,
|
||||
contextTypes,
|
||||
ReactPropTypeLocations.context
|
||||
);
|
||||
Constructor.contextTypes = assign(
|
||||
{},
|
||||
Constructor.contextTypes,
|
||||
contextTypes
|
||||
);
|
||||
},
|
||||
/**
|
||||
* Special case getDefaultProps which should move into statics but requires
|
||||
* automatic merging.
|
||||
*/
|
||||
getDefaultProps: function(Constructor, getDefaultProps) {
|
||||
if (Constructor.getDefaultProps) {
|
||||
Constructor.getDefaultProps = createMergedResultFunction(
|
||||
Constructor.getDefaultProps,
|
||||
getDefaultProps
|
||||
);
|
||||
} else {
|
||||
Constructor.getDefaultProps = getDefaultProps;
|
||||
}
|
||||
},
|
||||
propTypes: function(Constructor, propTypes) {
|
||||
validateTypeDef(
|
||||
Constructor,
|
||||
propTypes,
|
||||
ReactPropTypeLocations.prop
|
||||
);
|
||||
Constructor.propTypes = assign(
|
||||
{},
|
||||
Constructor.propTypes,
|
||||
propTypes
|
||||
);
|
||||
},
|
||||
statics: function(Constructor, statics) {
|
||||
mixStaticSpecIntoComponent(Constructor, statics);
|
||||
}
|
||||
};
|
||||
|
||||
function getDeclarationErrorAddendum(component) {
|
||||
var owner = component._owner || null;
|
||||
if (owner && owner.constructor && owner.constructor.displayName) {
|
||||
|
@ -399,50 +40,6 @@ function getDeclarationErrorAddendum(component) {
|
|||
return '';
|
||||
}
|
||||
|
||||
function validateTypeDef(Constructor, typeDef, location) {
|
||||
for (var propName in typeDef) {
|
||||
if (typeDef.hasOwnProperty(propName)) {
|
||||
invariant(
|
||||
typeof typeDef[propName] == 'function',
|
||||
'%s: %s type `%s` is invalid; it must be a function, usually from ' +
|
||||
'React.PropTypes.',
|
||||
Constructor.displayName || 'ReactCompositeComponent',
|
||||
ReactPropTypeLocationNames[location],
|
||||
propName
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function validateMethodOverride(proto, name) {
|
||||
var specPolicy = ReactCompositeComponentInterface.hasOwnProperty(name) ?
|
||||
ReactCompositeComponentInterface[name] :
|
||||
null;
|
||||
|
||||
// Disallow overriding of base class methods unless explicitly allowed.
|
||||
if (ReactCompositeComponentMixin.hasOwnProperty(name)) {
|
||||
invariant(
|
||||
specPolicy === SpecPolicy.OVERRIDE_BASE,
|
||||
'ReactCompositeComponentInterface: 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 (proto.hasOwnProperty(name)) {
|
||||
invariant(
|
||||
specPolicy === SpecPolicy.DEFINE_MANY ||
|
||||
specPolicy === SpecPolicy.DEFINE_MANY_MERGED,
|
||||
'ReactCompositeComponentInterface: You are attempting to define ' +
|
||||
'`%s` on your component more than once. This conflict may be due ' +
|
||||
'to a mixin.',
|
||||
name
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function validateLifeCycleOnReplaceState(instance) {
|
||||
var compositeLifeCycleState = instance._compositeLifeCycleState;
|
||||
invariant(
|
||||
|
@ -462,206 +59,6 @@ function validateLifeCycleOnReplaceState(instance) {
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Mixin helper which handles policy validation and reserved
|
||||
* specification keys when building `ReactCompositeComponent` classses.
|
||||
*/
|
||||
function mixSpecIntoComponent(Constructor, spec) {
|
||||
if (!spec) {
|
||||
return;
|
||||
}
|
||||
|
||||
invariant(
|
||||
!ReactLegacyElement.isValidFactory(spec),
|
||||
'ReactCompositeComponent: You\'re attempting to ' +
|
||||
'use a component class as a mixin. Instead, just use a regular object.'
|
||||
);
|
||||
invariant(
|
||||
!ReactElement.isValidElement(spec),
|
||||
'ReactCompositeComponent: You\'re attempting to ' +
|
||||
'use a component as a mixin. Instead, just use a regular object.'
|
||||
);
|
||||
|
||||
var proto = Constructor.prototype;
|
||||
|
||||
// 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];
|
||||
validateMethodOverride(proto, 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 ReactCompositeComponent methods (in the "interface").
|
||||
// 2. Overridden methods (that were mixed in).
|
||||
var isCompositeComponentMethod =
|
||||
ReactCompositeComponentInterface.hasOwnProperty(name);
|
||||
var isAlreadyDefined = proto.hasOwnProperty(name);
|
||||
var markedDontBind = property && property.__reactDontBind;
|
||||
var isFunction = typeof property === 'function';
|
||||
var shouldAutoBind =
|
||||
isFunction &&
|
||||
!isCompositeComponentMethod &&
|
||||
!isAlreadyDefined &&
|
||||
!markedDontBind;
|
||||
|
||||
if (shouldAutoBind) {
|
||||
if (!proto.__reactAutoBindMap) {
|
||||
proto.__reactAutoBindMap = {};
|
||||
}
|
||||
proto.__reactAutoBindMap[name] = property;
|
||||
proto[name] = property;
|
||||
} else {
|
||||
if (isAlreadyDefined) {
|
||||
var specPolicy = ReactCompositeComponentInterface[name];
|
||||
|
||||
// These cases should already be caught by validateMethodOverride
|
||||
invariant(
|
||||
isCompositeComponentMethod && (
|
||||
specPolicy === SpecPolicy.DEFINE_MANY_MERGED ||
|
||||
specPolicy === SpecPolicy.DEFINE_MANY
|
||||
),
|
||||
'ReactCompositeComponent: 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 === SpecPolicy.DEFINE_MANY_MERGED) {
|
||||
proto[name] = createMergedResultFunction(proto[name], property);
|
||||
} else if (specPolicy === SpecPolicy.DEFINE_MANY) {
|
||||
proto[name] = createChainedFunction(proto[name], property);
|
||||
}
|
||||
} else {
|
||||
proto[name] = property;
|
||||
if (__DEV__) {
|
||||
// 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,
|
||||
'ReactCompositeComponent: 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,
|
||||
'ReactCompositeComponent: You are attempting to define ' +
|
||||
'`%s` on your component more than once. This conflict may be ' +
|
||||
'due to a mixin.',
|
||||
name
|
||||
);
|
||||
Constructor[name] = property;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge two objects, but throw if both contain the same key.
|
||||
*
|
||||
* @param {object} one The first object, which is mutated.
|
||||
* @param {object} two The second object
|
||||
* @return {object} one after it has been mutated to contain everything in two.
|
||||
*/
|
||||
function mergeObjectsWithNoDuplicateKeys(one, two) {
|
||||
invariant(
|
||||
one && two && typeof one === 'object' && typeof two === 'object',
|
||||
'mergeObjectsWithNoDuplicateKeys(): Cannot merge non-objects'
|
||||
);
|
||||
|
||||
mapObject(two, function(value, key) {
|
||||
invariant(
|
||||
one[key] === undefined,
|
||||
'mergeObjectsWithNoDuplicateKeys(): ' +
|
||||
'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
|
||||
);
|
||||
one[key] = value;
|
||||
});
|
||||
return one;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a function that invokes two functions and merges their return values.
|
||||
*
|
||||
* @param {function} one Function to invoke first.
|
||||
* @param {function} two Function to invoke second.
|
||||
* @return {function} Function that invokes the two argument functions.
|
||||
* @private
|
||||
*/
|
||||
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;
|
||||
}
|
||||
return mergeObjectsWithNoDuplicateKeys(a, b);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a function that invokes two functions and ignores their return vales.
|
||||
*
|
||||
* @param {function} one Function to invoke first.
|
||||
* @param {function} two Function to invoke second.
|
||||
* @return {function} Function that invokes the two argument functions.
|
||||
* @private
|
||||
*/
|
||||
function createChainedFunction(one, two) {
|
||||
return function chainedFunction() {
|
||||
one.apply(this, arguments);
|
||||
two.apply(this, arguments);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* `ReactCompositeComponent` maintains an auxiliary life cycle state in
|
||||
* `this._compositeLifeCycleState` (which can be null).
|
||||
|
@ -1380,77 +777,8 @@ var ReactCompositeComponent = {
|
|||
|
||||
LifeCycle: CompositeLifeCycle,
|
||||
|
||||
Base: ReactCompositeComponentBase,
|
||||
Base: ReactCompositeComponentBase
|
||||
|
||||
/**
|
||||
* Creates a composite component class given a class specification.
|
||||
*
|
||||
* @param {object} spec Class specification (which must define `render`).
|
||||
* @return {function} Component constructor function.
|
||||
* @public
|
||||
*/
|
||||
createClass: function(spec) {
|
||||
var Constructor = function(props) {
|
||||
// This constructor is overridden by mocks. The argument is used
|
||||
// by mocks to assert on what gets mounted. This will later be used
|
||||
// by the stand-alone class implementation.
|
||||
};
|
||||
Constructor.prototype = new ReactCompositeComponentBase();
|
||||
Constructor.prototype.constructor = Constructor;
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
invariant(
|
||||
Constructor.prototype.render,
|
||||
'createClass(...): Class specification must implement a `render` method.'
|
||||
);
|
||||
|
||||
if (__DEV__) {
|
||||
if (Constructor.prototype.componentShouldUpdate) {
|
||||
monitorCodeUse(
|
||||
'react_component_should_update_warning',
|
||||
{ component: spec.displayName }
|
||||
);
|
||||
console.warn(
|
||||
(spec.displayName || 'A component') + ' 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.'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Reduce time spent doing lookups by setting these on the prototype.
|
||||
for (var methodName in ReactCompositeComponentInterface) {
|
||||
if (!Constructor.prototype[methodName]) {
|
||||
Constructor.prototype[methodName] = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (__DEV__) {
|
||||
return ReactLegacyElement.wrapFactory(
|
||||
ReactElementValidator.createFactory(Constructor)
|
||||
);
|
||||
}
|
||||
return ReactLegacyElement.wrapFactory(
|
||||
ReactElement.createFactory(Constructor)
|
||||
);
|
||||
},
|
||||
|
||||
injection: {
|
||||
injectMixin: function(mixin) {
|
||||
injectedMixins.push(mixin);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = ReactCompositeComponent;
|
||||
|
|
|
@ -1302,7 +1302,7 @@ describe('ReactCompositeComponent', function() {
|
|||
}
|
||||
});
|
||||
}).toThrow(
|
||||
'Invariant Violation: ReactCompositeComponent: You are attempting to ' +
|
||||
'Invariant Violation: ReactClass: You are attempting to ' +
|
||||
'define a reserved property, `getDefaultProps`, that shouldn\'t be on ' +
|
||||
'the "statics" key. Define it as an instance property instead; it ' +
|
||||
'will still be accessible on the constructor.'
|
||||
|
@ -1353,7 +1353,7 @@ describe('ReactCompositeComponent', function() {
|
|||
}
|
||||
});
|
||||
}).toThrow(
|
||||
'Invariant Violation: ReactCompositeComponent: You are attempting to ' +
|
||||
'Invariant Violation: ReactClass: You are attempting to ' +
|
||||
'define `abc` on your component more than once. This conflict may be ' +
|
||||
'due to a mixin.'
|
||||
);
|
||||
|
@ -1378,7 +1378,7 @@ describe('ReactCompositeComponent', function() {
|
|||
}
|
||||
});
|
||||
}).toThrow(
|
||||
'Invariant Violation: ReactCompositeComponent: You are attempting to ' +
|
||||
'Invariant Violation: ReactClass: You are attempting to ' +
|
||||
'define `abc` on your component more than once. This conflict may be ' +
|
||||
'due to a mixin.'
|
||||
);
|
||||
|
@ -1394,7 +1394,7 @@ describe('ReactCompositeComponent', function() {
|
|||
}
|
||||
});
|
||||
}).toThrow(
|
||||
'Invariant Violation: ReactCompositeComponent: You\'re attempting to ' +
|
||||
'Invariant Violation: ReactClass: You\'re attempting to ' +
|
||||
'use a component as a mixin. Instead, just use a regular object.'
|
||||
);
|
||||
});
|
||||
|
@ -1415,7 +1415,7 @@ describe('ReactCompositeComponent', function() {
|
|||
}
|
||||
});
|
||||
}).toThrow(
|
||||
'Invariant Violation: ReactCompositeComponent: You\'re attempting to ' +
|
||||
'Invariant Violation: ReactClass: You\'re attempting to ' +
|
||||
'use a component class as a mixin. Instead, just use a regular object.'
|
||||
);
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue