mirror of
https://github.com/facebook/react.git
synced 2026-02-26 18:58:05 +00:00
1429 lines
44 KiB
JavaScript
1429 lines
44 KiB
JavaScript
/**
|
|
* Copyright 2013-2014, Facebook, Inc.
|
|
* All rights reserved.
|
|
*
|
|
* This source code is licensed under the BSD-style license found in the
|
|
* LICENSE file in the root directory of this source tree. An additional grant
|
|
* of patent rights can be found in the PATENTS file in the same directory.
|
|
*
|
|
* @providesModule ReactCompositeComponent
|
|
*/
|
|
|
|
"use strict";
|
|
|
|
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 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 = Object.assign(
|
|
{},
|
|
Constructor.childContextTypes,
|
|
childContextTypes
|
|
);
|
|
},
|
|
contextTypes: function(Constructor, contextTypes) {
|
|
validateTypeDef(
|
|
Constructor,
|
|
contextTypes,
|
|
ReactPropTypeLocations.context
|
|
);
|
|
Constructor.contextTypes = Object.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 = Object.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) {
|
|
return ' Check the render method of `' + owner.constructor.displayName +
|
|
'`.';
|
|
}
|
|
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(
|
|
instance.isMounted() ||
|
|
compositeLifeCycleState === CompositeLifeCycle.MOUNTING,
|
|
'replaceState(...): Can only update a mounted or mounting component.'
|
|
);
|
|
invariant(
|
|
ReactCurrentOwner.current == null,
|
|
'replaceState(...): Cannot update during an existing state transition ' +
|
|
'(such as within `render`). Render methods should be a pure function ' +
|
|
'of props and state.'
|
|
);
|
|
invariant(compositeLifeCycleState !== CompositeLifeCycle.UNMOUNTING,
|
|
'replaceState(...): Cannot update while unmounting component. This ' +
|
|
'usually means you called setState() on an unmounted component.'
|
|
);
|
|
}
|
|
|
|
/**
|
|
* 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).
|
|
*
|
|
* This is different from the life cycle state maintained by `ReactComponent` in
|
|
* `this._lifeCycleState`. The following diagram shows how the states overlap in
|
|
* time. There are times when the CompositeLifeCycle is null - at those times it
|
|
* is only meaningful to look at ComponentLifeCycle alone.
|
|
*
|
|
* Top Row: ReactComponent.ComponentLifeCycle
|
|
* Low Row: ReactComponent.CompositeLifeCycle
|
|
*
|
|
* +-------+---------------------------------+--------+
|
|
* | UN | MOUNTED | UN |
|
|
* |MOUNTED| | MOUNTED|
|
|
* +-------+---------------------------------+--------+
|
|
* | ^--------+ +-------+ +--------^ |
|
|
* | | | | | | | |
|
|
* | 0--|MOUNTING|-0-|RECEIVE|-0-| UN |--->0 |
|
|
* | | | |PROPS | |MOUNTING| |
|
|
* | | | | | | | |
|
|
* | | | | | | | |
|
|
* | +--------+ +-------+ +--------+ |
|
|
* | | | |
|
|
* +-------+---------------------------------+--------+
|
|
*/
|
|
var CompositeLifeCycle = keyMirror({
|
|
/**
|
|
* Components in the process of being mounted respond to state changes
|
|
* differently.
|
|
*/
|
|
MOUNTING: null,
|
|
/**
|
|
* Components in the process of being unmounted are guarded against state
|
|
* changes.
|
|
*/
|
|
UNMOUNTING: null,
|
|
/**
|
|
* Components that are mounted and receiving new props respond to state
|
|
* changes differently.
|
|
*/
|
|
RECEIVING_PROPS: null
|
|
});
|
|
|
|
/**
|
|
* @lends {ReactCompositeComponent.prototype}
|
|
*/
|
|
var ReactCompositeComponentMixin = {
|
|
|
|
/**
|
|
* Base constructor for all composite component.
|
|
*
|
|
* @param {ReactElement} element
|
|
* @final
|
|
* @internal
|
|
*/
|
|
construct: function(element) {
|
|
// Children can be either an array or more than one argument
|
|
ReactComponent.Mixin.construct.apply(this, arguments);
|
|
ReactOwner.Mixin.construct.apply(this, arguments);
|
|
|
|
this.state = null;
|
|
this._pendingState = null;
|
|
|
|
// This is the public post-processed context. The real context and pending
|
|
// context lives on the element.
|
|
this.context = null;
|
|
|
|
this._compositeLifeCycleState = null;
|
|
},
|
|
|
|
/**
|
|
* Checks whether or not this composite component is mounted.
|
|
* @return {boolean} True if mounted, false otherwise.
|
|
* @protected
|
|
* @final
|
|
*/
|
|
isMounted: function() {
|
|
return ReactComponent.Mixin.isMounted.call(this) &&
|
|
this._compositeLifeCycleState !== CompositeLifeCycle.MOUNTING;
|
|
},
|
|
|
|
/**
|
|
* Initializes the component, renders markup, and registers event listeners.
|
|
*
|
|
* @param {string} rootID DOM ID of the root node.
|
|
* @param {ReactReconcileTransaction|ReactServerRenderingTransaction} transaction
|
|
* @param {number} mountDepth number of components in the owner hierarchy
|
|
* @return {?string} Rendered markup to be inserted into the DOM.
|
|
* @final
|
|
* @internal
|
|
*/
|
|
mountComponent: ReactPerf.measure(
|
|
'ReactCompositeComponent',
|
|
'mountComponent',
|
|
function(rootID, transaction, mountDepth) {
|
|
ReactComponent.Mixin.mountComponent.call(
|
|
this,
|
|
rootID,
|
|
transaction,
|
|
mountDepth
|
|
);
|
|
this._compositeLifeCycleState = CompositeLifeCycle.MOUNTING;
|
|
|
|
if (this.__reactAutoBindMap) {
|
|
this._bindAutoBindMethods();
|
|
}
|
|
|
|
this.context = this._processContext(this._currentElement._context);
|
|
this.props = this._processProps(this.props);
|
|
|
|
this.state = this.getInitialState ? this.getInitialState() : null;
|
|
invariant(
|
|
typeof this.state === 'object' && !Array.isArray(this.state),
|
|
'%s.getInitialState(): must return an object or null',
|
|
this.constructor.displayName || 'ReactCompositeComponent'
|
|
);
|
|
|
|
this._pendingState = null;
|
|
this._pendingForceUpdate = false;
|
|
|
|
if (this.componentWillMount) {
|
|
this.componentWillMount();
|
|
// When mounting, calls to `setState` by `componentWillMount` will set
|
|
// `this._pendingState` without triggering a re-render.
|
|
if (this._pendingState) {
|
|
this.state = this._pendingState;
|
|
this._pendingState = null;
|
|
}
|
|
}
|
|
|
|
this._renderedComponent = instantiateReactComponent(
|
|
this._renderValidatedComponent(),
|
|
this._currentElement.type // The wrapping type
|
|
);
|
|
|
|
// Done with mounting, `setState` will now trigger UI changes.
|
|
this._compositeLifeCycleState = null;
|
|
var markup = this._renderedComponent.mountComponent(
|
|
rootID,
|
|
transaction,
|
|
mountDepth + 1
|
|
);
|
|
if (this.componentDidMount) {
|
|
transaction.getReactMountReady().enqueue(this.componentDidMount, this);
|
|
}
|
|
return markup;
|
|
}
|
|
),
|
|
|
|
/**
|
|
* Releases any resources allocated by `mountComponent`.
|
|
*
|
|
* @final
|
|
* @internal
|
|
*/
|
|
unmountComponent: function() {
|
|
this._compositeLifeCycleState = CompositeLifeCycle.UNMOUNTING;
|
|
if (this.componentWillUnmount) {
|
|
this.componentWillUnmount();
|
|
}
|
|
this._compositeLifeCycleState = null;
|
|
|
|
this._renderedComponent.unmountComponent();
|
|
this._renderedComponent = null;
|
|
|
|
ReactComponent.Mixin.unmountComponent.call(this);
|
|
|
|
// Some existing components rely on this.props even after they've been
|
|
// destroyed (in event handlers).
|
|
// TODO: this.props = null;
|
|
// TODO: this.state = null;
|
|
},
|
|
|
|
/**
|
|
* Sets a subset of the state. Always use this or `replaceState` to mutate
|
|
* state. You should treat `this.state` as immutable.
|
|
*
|
|
* There is no guarantee that `this.state` will be immediately updated, so
|
|
* accessing `this.state` after calling this method may return the old value.
|
|
*
|
|
* There is no guarantee that calls to `setState` will run synchronously,
|
|
* as they may eventually be batched together. You can provide an optional
|
|
* callback that will be executed when the call to setState is actually
|
|
* completed.
|
|
*
|
|
* @param {object} partialState Next partial state to be merged with state.
|
|
* @param {?function} callback Called after state is updated.
|
|
* @final
|
|
* @protected
|
|
*/
|
|
setState: function(partialState, callback) {
|
|
invariant(
|
|
typeof partialState === 'object' || partialState == null,
|
|
'setState(...): takes an object of state variables to update.'
|
|
);
|
|
if (__DEV__){
|
|
warning(
|
|
partialState != null,
|
|
'setState(...): You passed an undefined or null state object; ' +
|
|
'instead, use forceUpdate().'
|
|
);
|
|
}
|
|
// Merge with `_pendingState` if it exists, otherwise with existing state.
|
|
this.replaceState(
|
|
Object.assign({}, this._pendingState || this.state, partialState),
|
|
callback
|
|
);
|
|
},
|
|
|
|
/**
|
|
* Replaces all of the state. Always use this or `setState` to mutate state.
|
|
* You should treat `this.state` as immutable.
|
|
*
|
|
* There is no guarantee that `this.state` will be immediately updated, so
|
|
* accessing `this.state` after calling this method may return the old value.
|
|
*
|
|
* @param {object} completeState Next state.
|
|
* @param {?function} callback Called after state is updated.
|
|
* @final
|
|
* @protected
|
|
*/
|
|
replaceState: function(completeState, callback) {
|
|
validateLifeCycleOnReplaceState(this);
|
|
this._pendingState = completeState;
|
|
if (this._compositeLifeCycleState !== CompositeLifeCycle.MOUNTING) {
|
|
// If we're in a componentWillMount handler, don't enqueue a rerender
|
|
// because ReactUpdates assumes we're in a browser context (which is wrong
|
|
// for server rendering) and we're about to do a render anyway.
|
|
// TODO: The callback here is ignored when setState is called from
|
|
// componentWillMount. Either fix it or disallow doing so completely in
|
|
// favor of getInitialState.
|
|
ReactUpdates.enqueueUpdate(this, callback);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Filters the context object to only contain keys specified in
|
|
* `contextTypes`, and asserts that they are valid.
|
|
*
|
|
* @param {object} context
|
|
* @return {?object}
|
|
* @private
|
|
*/
|
|
_processContext: function(context) {
|
|
var maskedContext = null;
|
|
var contextTypes = this.constructor.contextTypes;
|
|
if (contextTypes) {
|
|
maskedContext = {};
|
|
for (var contextName in contextTypes) {
|
|
maskedContext[contextName] = context[contextName];
|
|
}
|
|
if (__DEV__) {
|
|
this._checkPropTypes(
|
|
contextTypes,
|
|
maskedContext,
|
|
ReactPropTypeLocations.context
|
|
);
|
|
}
|
|
}
|
|
return maskedContext;
|
|
},
|
|
|
|
/**
|
|
* @param {object} currentContext
|
|
* @return {object}
|
|
* @private
|
|
*/
|
|
_processChildContext: function(currentContext) {
|
|
var childContext = this.getChildContext && this.getChildContext();
|
|
var displayName = this.constructor.displayName || 'ReactCompositeComponent';
|
|
if (childContext) {
|
|
invariant(
|
|
typeof this.constructor.childContextTypes === 'object',
|
|
'%s.getChildContext(): childContextTypes must be defined in order to ' +
|
|
'use getChildContext().',
|
|
displayName
|
|
);
|
|
if (__DEV__) {
|
|
this._checkPropTypes(
|
|
this.constructor.childContextTypes,
|
|
childContext,
|
|
ReactPropTypeLocations.childContext
|
|
);
|
|
}
|
|
for (var name in childContext) {
|
|
invariant(
|
|
name in this.constructor.childContextTypes,
|
|
'%s.getChildContext(): key "%s" is not defined in childContextTypes.',
|
|
displayName,
|
|
name
|
|
);
|
|
}
|
|
return Object.assign({}, currentContext, childContext);
|
|
}
|
|
return currentContext;
|
|
},
|
|
|
|
/**
|
|
* Processes props by setting default values for unspecified props and
|
|
* asserting that the props are valid. Does not mutate its argument; returns
|
|
* a new props object with defaults merged in.
|
|
*
|
|
* @param {object} newProps
|
|
* @return {object}
|
|
* @private
|
|
*/
|
|
_processProps: function(newProps) {
|
|
if (__DEV__) {
|
|
var propTypes = this.constructor.propTypes;
|
|
if (propTypes) {
|
|
this._checkPropTypes(propTypes, newProps, ReactPropTypeLocations.prop);
|
|
}
|
|
}
|
|
return newProps;
|
|
},
|
|
|
|
/**
|
|
* Assert that the props are valid
|
|
*
|
|
* @param {object} propTypes Map of prop name to a ReactPropType
|
|
* @param {object} props
|
|
* @param {string} location e.g. "prop", "context", "child context"
|
|
* @private
|
|
*/
|
|
_checkPropTypes: function(propTypes, props, location) {
|
|
// TODO: Stop validating prop types here and only use the element
|
|
// validation.
|
|
var componentName = this.constructor.displayName;
|
|
for (var propName in propTypes) {
|
|
if (propTypes.hasOwnProperty(propName)) {
|
|
var error =
|
|
propTypes[propName](props, propName, componentName, location);
|
|
if (error instanceof Error) {
|
|
// We may want to extend this logic for similar errors in
|
|
// renderComponent calls, so I'm abstracting it away into
|
|
// a function to minimize refactoring in the future
|
|
var addendum = getDeclarationErrorAddendum(this);
|
|
warning(false, error.message + addendum);
|
|
}
|
|
}
|
|
}
|
|
},
|
|
|
|
/**
|
|
* If any of `_pendingElement`, `_pendingState`, or `_pendingForceUpdate`
|
|
* is set, update the component.
|
|
*
|
|
* @param {ReactReconcileTransaction} transaction
|
|
* @internal
|
|
*/
|
|
performUpdateIfNecessary: function(transaction) {
|
|
var compositeLifeCycleState = this._compositeLifeCycleState;
|
|
// Do not trigger a state transition if we are in the middle of mounting or
|
|
// receiving props because both of those will already be doing this.
|
|
if (compositeLifeCycleState === CompositeLifeCycle.MOUNTING ||
|
|
compositeLifeCycleState === CompositeLifeCycle.RECEIVING_PROPS) {
|
|
return;
|
|
}
|
|
|
|
if (this._pendingElement == null &&
|
|
this._pendingState == null &&
|
|
!this._pendingForceUpdate) {
|
|
return;
|
|
}
|
|
|
|
var nextContext = this.context;
|
|
var nextProps = this.props;
|
|
var nextElement = this._currentElement;
|
|
if (this._pendingElement != null) {
|
|
nextElement = this._pendingElement;
|
|
nextContext = this._processContext(nextElement._context);
|
|
nextProps = this._processProps(nextElement.props);
|
|
this._pendingElement = null;
|
|
|
|
this._compositeLifeCycleState = CompositeLifeCycle.RECEIVING_PROPS;
|
|
if (this.componentWillReceiveProps) {
|
|
this.componentWillReceiveProps(nextProps, nextContext);
|
|
}
|
|
}
|
|
|
|
this._compositeLifeCycleState = null;
|
|
|
|
var nextState = this._pendingState || this.state;
|
|
this._pendingState = null;
|
|
|
|
var shouldUpdate =
|
|
this._pendingForceUpdate ||
|
|
!this.shouldComponentUpdate ||
|
|
this.shouldComponentUpdate(nextProps, nextState, nextContext);
|
|
|
|
if (__DEV__) {
|
|
if (typeof shouldUpdate === "undefined") {
|
|
console.warn(
|
|
(this.constructor.displayName || 'ReactCompositeComponent') +
|
|
'.shouldComponentUpdate(): Returned undefined instead of a ' +
|
|
'boolean value. Make sure to return true or false.'
|
|
);
|
|
}
|
|
}
|
|
|
|
if (shouldUpdate) {
|
|
this._pendingForceUpdate = false;
|
|
// Will set `this.props`, `this.state` and `this.context`.
|
|
this._performComponentUpdate(
|
|
nextElement,
|
|
nextProps,
|
|
nextState,
|
|
nextContext,
|
|
transaction
|
|
);
|
|
} else {
|
|
// If it's determined that a component should not update, we still want
|
|
// to set props and state.
|
|
this._currentElement = nextElement;
|
|
this.props = nextProps;
|
|
this.state = nextState;
|
|
this.context = nextContext;
|
|
|
|
// Owner cannot change because shouldUpdateReactComponent doesn't allow
|
|
// it. TODO: Remove this._owner completely.
|
|
this._owner = nextElement._owner;
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Merges new props and state, notifies delegate methods of update and
|
|
* performs update.
|
|
*
|
|
* @param {ReactElement} nextElement Next element
|
|
* @param {object} nextProps Next public object to set as properties.
|
|
* @param {?object} nextState Next object to set as state.
|
|
* @param {?object} nextContext Next public object to set as context.
|
|
* @param {ReactReconcileTransaction} transaction
|
|
* @private
|
|
*/
|
|
_performComponentUpdate: function(
|
|
nextElement,
|
|
nextProps,
|
|
nextState,
|
|
nextContext,
|
|
transaction
|
|
) {
|
|
var prevElement = this._currentElement;
|
|
var prevProps = this.props;
|
|
var prevState = this.state;
|
|
var prevContext = this.context;
|
|
|
|
if (this.componentWillUpdate) {
|
|
this.componentWillUpdate(nextProps, nextState, nextContext);
|
|
}
|
|
|
|
this._currentElement = nextElement;
|
|
this.props = nextProps;
|
|
this.state = nextState;
|
|
this.context = nextContext;
|
|
|
|
// Owner cannot change because shouldUpdateReactComponent doesn't allow
|
|
// it. TODO: Remove this._owner completely.
|
|
this._owner = nextElement._owner;
|
|
|
|
this.updateComponent(
|
|
transaction,
|
|
prevElement
|
|
);
|
|
|
|
if (this.componentDidUpdate) {
|
|
transaction.getReactMountReady().enqueue(
|
|
this.componentDidUpdate.bind(this, prevProps, prevState, prevContext),
|
|
this
|
|
);
|
|
}
|
|
},
|
|
|
|
receiveComponent: function(nextElement, transaction) {
|
|
if (nextElement === this._currentElement &&
|
|
nextElement._owner != null) {
|
|
// Since elements are immutable after the owner is rendered,
|
|
// we can do a cheap identity compare here to determine if this is a
|
|
// superfluous reconcile. It's possible for state to be mutable but such
|
|
// change should trigger an update of the owner which would recreate
|
|
// the element. We explicitly check for the existence of an owner since
|
|
// it's possible for a element created outside a composite to be
|
|
// deeply mutated and reused.
|
|
return;
|
|
}
|
|
|
|
ReactComponent.Mixin.receiveComponent.call(
|
|
this,
|
|
nextElement,
|
|
transaction
|
|
);
|
|
},
|
|
|
|
/**
|
|
* 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
|
|
* @param {ReactElement} prevElement
|
|
* @internal
|
|
* @overridable
|
|
*/
|
|
updateComponent: ReactPerf.measure(
|
|
'ReactCompositeComponent',
|
|
'updateComponent',
|
|
function(transaction, prevParentElement) {
|
|
ReactComponent.Mixin.updateComponent.call(
|
|
this,
|
|
transaction,
|
|
prevParentElement
|
|
);
|
|
|
|
var prevComponentInstance = this._renderedComponent;
|
|
var prevElement = prevComponentInstance._currentElement;
|
|
var nextElement = this._renderValidatedComponent();
|
|
if (shouldUpdateReactComponent(prevElement, nextElement)) {
|
|
prevComponentInstance.receiveComponent(nextElement, transaction);
|
|
} else {
|
|
// These two IDs are actually the same! But nothing should rely on that.
|
|
var thisID = this._rootNodeID;
|
|
var prevComponentID = prevComponentInstance._rootNodeID;
|
|
prevComponentInstance.unmountComponent();
|
|
this._renderedComponent = instantiateReactComponent(
|
|
nextElement,
|
|
this._currentElement.type
|
|
);
|
|
var nextMarkup = this._renderedComponent.mountComponent(
|
|
thisID,
|
|
transaction,
|
|
this._mountDepth + 1
|
|
);
|
|
ReactComponent.BackendIDOperations.dangerouslyReplaceNodeWithMarkupByID(
|
|
prevComponentID,
|
|
nextMarkup
|
|
);
|
|
}
|
|
}
|
|
),
|
|
|
|
/**
|
|
* Forces an update. This should only be invoked when it is known with
|
|
* certainty that we are **not** in a DOM transaction.
|
|
*
|
|
* You may want to call this when you know that some deeper aspect of the
|
|
* component's state has changed but `setState` was not called.
|
|
*
|
|
* This will not invoke `shouldUpdateComponent`, but it will invoke
|
|
* `componentWillUpdate` and `componentDidUpdate`.
|
|
*
|
|
* @param {?function} callback Called after update is complete.
|
|
* @final
|
|
* @protected
|
|
*/
|
|
forceUpdate: function(callback) {
|
|
var compositeLifeCycleState = this._compositeLifeCycleState;
|
|
invariant(
|
|
this.isMounted() ||
|
|
compositeLifeCycleState === CompositeLifeCycle.MOUNTING,
|
|
'forceUpdate(...): Can only force an update on mounted or mounting ' +
|
|
'components.'
|
|
);
|
|
invariant(
|
|
compositeLifeCycleState !== CompositeLifeCycle.UNMOUNTING &&
|
|
ReactCurrentOwner.current == null,
|
|
'forceUpdate(...): Cannot force an update while unmounting component ' +
|
|
'or within a `render` function.'
|
|
);
|
|
this._pendingForceUpdate = true;
|
|
ReactUpdates.enqueueUpdate(this, callback);
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
_renderValidatedComponent: ReactPerf.measure(
|
|
'ReactCompositeComponent',
|
|
'_renderValidatedComponent',
|
|
function() {
|
|
var renderedComponent;
|
|
var previousContext = ReactContext.current;
|
|
ReactContext.current = this._processChildContext(
|
|
this._currentElement._context
|
|
);
|
|
ReactCurrentOwner.current = this;
|
|
try {
|
|
renderedComponent = this.render();
|
|
if (renderedComponent === null || renderedComponent === false) {
|
|
renderedComponent = ReactEmptyComponent.getEmptyComponent();
|
|
ReactEmptyComponent.registerNullComponentID(this._rootNodeID);
|
|
} else {
|
|
ReactEmptyComponent.deregisterNullComponentID(this._rootNodeID);
|
|
}
|
|
} finally {
|
|
ReactContext.current = previousContext;
|
|
ReactCurrentOwner.current = null;
|
|
}
|
|
invariant(
|
|
ReactElement.isValidElement(renderedComponent),
|
|
'%s.render(): A valid ReactComponent must be returned. You may have ' +
|
|
'returned undefined, an array or some other invalid object.',
|
|
this.constructor.displayName || 'ReactCompositeComponent'
|
|
);
|
|
return renderedComponent;
|
|
}
|
|
),
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
_bindAutoBindMethods: function() {
|
|
for (var autoBindKey in this.__reactAutoBindMap) {
|
|
if (!this.__reactAutoBindMap.hasOwnProperty(autoBindKey)) {
|
|
continue;
|
|
}
|
|
var method = this.__reactAutoBindMap[autoBindKey];
|
|
this[autoBindKey] = this._bindAutoBindMethod(ReactErrorUtils.guard(
|
|
method,
|
|
this.constructor.displayName + '.' + autoBindKey
|
|
));
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Binds a method to the component.
|
|
*
|
|
* @param {function} method Method to be bound.
|
|
* @private
|
|
*/
|
|
_bindAutoBindMethod: function(method) {
|
|
var component = this;
|
|
var boundMethod = method.bind(component);
|
|
if (__DEV__) {
|
|
boundMethod.__reactBoundContext = component;
|
|
boundMethod.__reactBoundMethod = method;
|
|
boundMethod.__reactBoundArguments = null;
|
|
var componentName = component.constructor.displayName;
|
|
var _bind = boundMethod.bind;
|
|
boundMethod.bind = function(newThis, ...args) {
|
|
// User is trying to bind() an autobound method; we effectively will
|
|
// ignore the value of "this" that the user is trying to use, so
|
|
// let's warn.
|
|
if (newThis !== component && newThis !== null) {
|
|
monitorCodeUse('react_bind_warning', { component: componentName });
|
|
console.warn(
|
|
'bind(): React component methods may only be bound to the ' +
|
|
'component instance. See ' + componentName
|
|
);
|
|
} else if (!args.length) {
|
|
monitorCodeUse('react_bind_warning', { component: componentName });
|
|
console.warn(
|
|
'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 ' + componentName
|
|
);
|
|
return boundMethod;
|
|
}
|
|
var reboundMethod = _bind.apply(boundMethod, arguments);
|
|
reboundMethod.__reactBoundContext = component;
|
|
reboundMethod.__reactBoundMethod = method;
|
|
reboundMethod.__reactBoundArguments = args;
|
|
return reboundMethod;
|
|
};
|
|
}
|
|
return boundMethod;
|
|
}
|
|
};
|
|
|
|
var ReactCompositeComponentBase = function() {};
|
|
Object.assign(
|
|
ReactCompositeComponentBase.prototype,
|
|
ReactComponent.Mixin,
|
|
ReactOwner.Mixin,
|
|
ReactPropTransferer.Mixin,
|
|
ReactCompositeComponentMixin
|
|
);
|
|
|
|
/**
|
|
* Module for creating composite components.
|
|
*
|
|
* @class ReactCompositeComponent
|
|
* @extends ReactComponent
|
|
* @extends ReactOwner
|
|
* @extends ReactPropTransferer
|
|
*/
|
|
var ReactCompositeComponent = {
|
|
|
|
LifeCycle: CompositeLifeCycle,
|
|
|
|
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;
|