You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
894 lines
28 KiB
894 lines
28 KiB
/** |
|
* Copyright 2013-2015, 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 ReactComponentEnvironment = require("./ReactComponentEnvironment"); |
|
var ReactContext = require("./ReactContext"); |
|
var ReactCurrentOwner = require("./ReactCurrentOwner"); |
|
var ReactElement = require("./ReactElement"); |
|
var ReactElementValidator = require("./ReactElementValidator"); |
|
var ReactInstanceMap = require("./ReactInstanceMap"); |
|
var ReactLifeCycle = require("./ReactLifeCycle"); |
|
var ReactNativeComponent = require("./ReactNativeComponent"); |
|
var ReactPerf = require("./ReactPerf"); |
|
var ReactPropTypeLocations = require("./ReactPropTypeLocations"); |
|
var ReactPropTypeLocationNames = require("./ReactPropTypeLocationNames"); |
|
var ReactReconciler = require("./ReactReconciler"); |
|
var ReactUpdates = require("./ReactUpdates"); |
|
|
|
var assign = require("./Object.assign"); |
|
var emptyObject = require("./emptyObject"); |
|
var invariant = require("./invariant"); |
|
var shouldUpdateReactComponent = require("./shouldUpdateReactComponent"); |
|
var warning = require("./warning"); |
|
|
|
function getDeclarationErrorAddendum(component) { |
|
var owner = component._currentElement._owner || null; |
|
if (owner) { |
|
var name = owner.getName(); |
|
if (name) { |
|
return ' Check the render method of `' + name + '`.'; |
|
} |
|
} |
|
return ''; |
|
} |
|
|
|
/** |
|
* ------------------ The Life-Cycle of a Composite Component ------------------ |
|
* |
|
* - constructor: Initialization of state. The instance is now retained. |
|
* - componentWillMount |
|
* - render |
|
* - [children's constructors] |
|
* - [children's componentWillMount and render] |
|
* - [children's componentDidMount] |
|
* - componentDidMount |
|
* |
|
* Update Phases: |
|
* - componentWillReceiveProps (only called if parent updated) |
|
* - shouldComponentUpdate |
|
* - componentWillUpdate |
|
* - render |
|
* - [children's constructors or receive props phases] |
|
* - componentDidUpdate |
|
* |
|
* - componentWillUnmount |
|
* - [children's componentWillUnmount] |
|
* - [children destroyed] |
|
* - (destroyed): The instance is now blank, released by React and ready for GC. |
|
* |
|
* ----------------------------------------------------------------------------- |
|
*/ |
|
|
|
/** |
|
* An incrementing ID assigned to each component when it is mounted. This is |
|
* used to enforce the order in which `ReactUpdates` updates dirty components. |
|
* |
|
* @private |
|
*/ |
|
var nextMountID = 1; |
|
|
|
/** |
|
* @lends {ReactCompositeComponent.prototype} |
|
*/ |
|
var ReactCompositeComponentMixin = { |
|
|
|
/** |
|
* Base constructor for all composite component. |
|
* |
|
* @param {ReactElement} element |
|
* @final |
|
* @internal |
|
*/ |
|
construct: function(element) { |
|
this._currentElement = element; |
|
this._rootNodeID = null; |
|
this._instance = null; |
|
|
|
// See ReactUpdateQueue |
|
this._pendingElement = null; |
|
this._pendingStateQueue = null; |
|
this._pendingReplaceState = false; |
|
this._pendingForceUpdate = false; |
|
|
|
this._renderedComponent = null; |
|
|
|
this._context = null; |
|
this._mountOrder = 0; |
|
this._isTopLevel = false; |
|
|
|
// See ReactUpdates and ReactUpdateQueue. |
|
this._pendingCallbacks = null; |
|
}, |
|
|
|
/** |
|
* Initializes the component, renders markup, and registers event listeners. |
|
* |
|
* @param {string} rootID DOM ID of the root node. |
|
* @param {ReactReconcileTransaction|ReactServerRenderingTransaction} transaction |
|
* @return {?string} Rendered markup to be inserted into the DOM. |
|
* @final |
|
* @internal |
|
*/ |
|
mountComponent: function(rootID, transaction, context) { |
|
this._context = context; |
|
this._mountOrder = nextMountID++; |
|
this._rootNodeID = rootID; |
|
|
|
var publicProps = this._processProps(this._currentElement.props); |
|
var publicContext = this._processContext(this._currentElement._context); |
|
|
|
var Component = ReactNativeComponent.getComponentClassForElement( |
|
this._currentElement |
|
); |
|
|
|
// Initialize the public class |
|
var inst = new Component(publicProps, publicContext); |
|
|
|
if ("production" !== process.env.NODE_ENV) { |
|
// This will throw later in _renderValidatedComponent, but add an early |
|
// warning now to help debugging |
|
("production" !== process.env.NODE_ENV ? warning( |
|
inst.render != null, |
|
'%s(...): No `render` method found on the returned component ' + |
|
'instance: you may have forgotten to define `render` in your ' + |
|
'component or you may have accidentally tried to render an element ' + |
|
'whose type is a function that isn\'t a React component.', |
|
Component.displayName || Component.name || 'Component' |
|
) : null); |
|
} |
|
|
|
// These should be set up in the constructor, but as a convenience for |
|
// simpler class abstractions, we set them up after the fact. |
|
inst.props = publicProps; |
|
inst.context = publicContext; |
|
inst.refs = emptyObject; |
|
|
|
this._instance = inst; |
|
|
|
// Store a reference from the instance back to the internal representation |
|
ReactInstanceMap.set(inst, this); |
|
|
|
if ("production" !== process.env.NODE_ENV) { |
|
this._warnIfContextsDiffer(this._currentElement._context, context); |
|
} |
|
|
|
if ("production" !== process.env.NODE_ENV) { |
|
// Since plain JS classes are defined without any special initialization |
|
// logic, we can not catch common errors early. Therefore, we have to |
|
// catch them here, at initialization time, instead. |
|
("production" !== process.env.NODE_ENV ? warning( |
|
!inst.getInitialState || |
|
inst.getInitialState.isReactClassApproved, |
|
'getInitialState was defined on %s, a plain JavaScript class. ' + |
|
'This is only supported for classes created using React.createClass. ' + |
|
'Did you mean to define a state property instead?', |
|
this.getName() || 'a component' |
|
) : null); |
|
("production" !== process.env.NODE_ENV ? warning( |
|
!inst.getDefaultProps || |
|
inst.getDefaultProps.isReactClassApproved, |
|
'getDefaultProps was defined on %s, a plain JavaScript class. ' + |
|
'This is only supported for classes created using React.createClass. ' + |
|
'Use a static property to define defaultProps instead.', |
|
this.getName() || 'a component' |
|
) : null); |
|
("production" !== process.env.NODE_ENV ? warning( |
|
!inst.propTypes, |
|
'propTypes was defined as an instance property on %s. Use a static ' + |
|
'property to define propTypes instead.', |
|
this.getName() || 'a component' |
|
) : null); |
|
("production" !== process.env.NODE_ENV ? warning( |
|
!inst.contextTypes, |
|
'contextTypes was defined as an instance property on %s. Use a ' + |
|
'static property to define contextTypes instead.', |
|
this.getName() || 'a component' |
|
) : null); |
|
("production" !== process.env.NODE_ENV ? warning( |
|
typeof inst.componentShouldUpdate !== 'function', |
|
'%s has a method called ' + |
|
'componentShouldUpdate(). Did you mean shouldComponentUpdate()? ' + |
|
'The name is phrased as a question because the function is ' + |
|
'expected to return a value.', |
|
(this.getName() || 'A component') |
|
) : null); |
|
} |
|
|
|
var initialState = inst.state; |
|
if (initialState === undefined) { |
|
inst.state = initialState = null; |
|
} |
|
("production" !== process.env.NODE_ENV ? invariant( |
|
typeof initialState === 'object' && !Array.isArray(initialState), |
|
'%s.state: must be set to an object or null', |
|
this.getName() || 'ReactCompositeComponent' |
|
) : invariant(typeof initialState === 'object' && !Array.isArray(initialState))); |
|
|
|
this._pendingStateQueue = null; |
|
this._pendingReplaceState = false; |
|
this._pendingForceUpdate = false; |
|
|
|
var renderedElement; |
|
|
|
var previouslyMounting = ReactLifeCycle.currentlyMountingInstance; |
|
ReactLifeCycle.currentlyMountingInstance = this; |
|
try { |
|
if (inst.componentWillMount) { |
|
inst.componentWillMount(); |
|
// When mounting, calls to `setState` by `componentWillMount` will set |
|
// `this._pendingStateQueue` without triggering a re-render. |
|
if (this._pendingStateQueue) { |
|
inst.state = this._processPendingState(inst.props, inst.context); |
|
} |
|
} |
|
|
|
renderedElement = this._renderValidatedComponent(); |
|
} finally { |
|
ReactLifeCycle.currentlyMountingInstance = previouslyMounting; |
|
} |
|
|
|
this._renderedComponent = this._instantiateReactComponent( |
|
renderedElement, |
|
this._currentElement.type // The wrapping type |
|
); |
|
|
|
var markup = ReactReconciler.mountComponent( |
|
this._renderedComponent, |
|
rootID, |
|
transaction, |
|
this._processChildContext(context) |
|
); |
|
if (inst.componentDidMount) { |
|
transaction.getReactMountReady().enqueue(inst.componentDidMount, inst); |
|
} |
|
|
|
return markup; |
|
}, |
|
|
|
/** |
|
* Releases any resources allocated by `mountComponent`. |
|
* |
|
* @final |
|
* @internal |
|
*/ |
|
unmountComponent: function() { |
|
var inst = this._instance; |
|
|
|
if (inst.componentWillUnmount) { |
|
var previouslyUnmounting = ReactLifeCycle.currentlyUnmountingInstance; |
|
ReactLifeCycle.currentlyUnmountingInstance = this; |
|
try { |
|
inst.componentWillUnmount(); |
|
} finally { |
|
ReactLifeCycle.currentlyUnmountingInstance = previouslyUnmounting; |
|
} |
|
} |
|
|
|
ReactReconciler.unmountComponent(this._renderedComponent); |
|
this._renderedComponent = null; |
|
|
|
// Reset pending fields |
|
this._pendingStateQueue = null; |
|
this._pendingReplaceState = false; |
|
this._pendingForceUpdate = false; |
|
this._pendingCallbacks = null; |
|
this._pendingElement = null; |
|
|
|
// These fields do not really need to be reset since this object is no |
|
// longer accessible. |
|
this._context = null; |
|
this._rootNodeID = null; |
|
|
|
// Delete the reference from the instance to this internal representation |
|
// which allow the internals to be properly cleaned up even if the user |
|
// leaks a reference to the public instance. |
|
ReactInstanceMap.remove(inst); |
|
|
|
// Some existing components rely on inst.props even after they've been |
|
// destroyed (in event handlers). |
|
// TODO: inst.props = null; |
|
// TODO: inst.state = null; |
|
// TODO: inst.context = null; |
|
}, |
|
|
|
/** |
|
* Schedule a partial update to the props. Only used for internal testing. |
|
* |
|
* @param {object} partialProps Subset of the next props. |
|
* @param {?function} callback Called after props are updated. |
|
* @final |
|
* @internal |
|
*/ |
|
_setPropsInternal: function(partialProps, callback) { |
|
// This is a deoptimized path. We optimize for always having an element. |
|
// This creates an extra internal element. |
|
var element = this._pendingElement || this._currentElement; |
|
this._pendingElement = ReactElement.cloneAndReplaceProps( |
|
element, |
|
assign({}, element.props, partialProps) |
|
); |
|
ReactUpdates.enqueueUpdate(this, callback); |
|
}, |
|
|
|
/** |
|
* Filters the context object to only contain keys specified in |
|
* `contextTypes` |
|
* |
|
* @param {object} context |
|
* @return {?object} |
|
* @private |
|
*/ |
|
_maskContext: function(context) { |
|
var maskedContext = null; |
|
// This really should be getting the component class for the element, |
|
// but we know that we're not going to need it for built-ins. |
|
if (typeof this._currentElement.type === 'string') { |
|
return emptyObject; |
|
} |
|
var contextTypes = this._currentElement.type.contextTypes; |
|
if (!contextTypes) { |
|
return emptyObject; |
|
} |
|
maskedContext = {}; |
|
for (var contextName in contextTypes) { |
|
maskedContext[contextName] = context[contextName]; |
|
} |
|
return maskedContext; |
|
}, |
|
|
|
/** |
|
* 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 = this._maskContext(context); |
|
if ("production" !== process.env.NODE_ENV) { |
|
var Component = ReactNativeComponent.getComponentClassForElement( |
|
this._currentElement |
|
); |
|
if (Component.contextTypes) { |
|
this._checkPropTypes( |
|
Component.contextTypes, |
|
maskedContext, |
|
ReactPropTypeLocations.context |
|
); |
|
} |
|
} |
|
return maskedContext; |
|
}, |
|
|
|
/** |
|
* @param {object} currentContext |
|
* @return {object} |
|
* @private |
|
*/ |
|
_processChildContext: function(currentContext) { |
|
var inst = this._instance; |
|
var childContext = inst.getChildContext && inst.getChildContext(); |
|
if (childContext) { |
|
("production" !== process.env.NODE_ENV ? invariant( |
|
typeof inst.constructor.childContextTypes === 'object', |
|
'%s.getChildContext(): childContextTypes must be defined in order to ' + |
|
'use getChildContext().', |
|
this.getName() || 'ReactCompositeComponent' |
|
) : invariant(typeof inst.constructor.childContextTypes === 'object')); |
|
if ("production" !== process.env.NODE_ENV) { |
|
this._checkPropTypes( |
|
inst.constructor.childContextTypes, |
|
childContext, |
|
ReactPropTypeLocations.childContext |
|
); |
|
} |
|
for (var name in childContext) { |
|
("production" !== process.env.NODE_ENV ? invariant( |
|
name in inst.constructor.childContextTypes, |
|
'%s.getChildContext(): key "%s" is not defined in childContextTypes.', |
|
this.getName() || 'ReactCompositeComponent', |
|
name |
|
) : invariant(name in inst.constructor.childContextTypes)); |
|
} |
|
return 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 ("production" !== process.env.NODE_ENV) { |
|
var Component = ReactNativeComponent.getComponentClassForElement( |
|
this._currentElement |
|
); |
|
if (Component.propTypes) { |
|
this._checkPropTypes( |
|
Component.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.getName(); |
|
for (var propName in propTypes) { |
|
if (propTypes.hasOwnProperty(propName)) { |
|
var error; |
|
try { |
|
// This is intentionally an invariant that gets caught. It's the same |
|
// behavior as without this statement except with a better message. |
|
("production" !== process.env.NODE_ENV ? invariant( |
|
typeof propTypes[propName] === 'function', |
|
'%s: %s type `%s` is invalid; it must be a function, usually ' + |
|
'from React.PropTypes.', |
|
componentName || 'React class', |
|
ReactPropTypeLocationNames[location], |
|
propName |
|
) : invariant(typeof propTypes[propName] === 'function')); |
|
error = propTypes[propName](props, propName, componentName, location); |
|
} catch (ex) { |
|
error = ex; |
|
} |
|
if (error instanceof Error) { |
|
// We may want to extend this logic for similar errors in |
|
// React.render calls, so I'm abstracting it away into |
|
// a function to minimize refactoring in the future |
|
var addendum = getDeclarationErrorAddendum(this); |
|
|
|
if (location === ReactPropTypeLocations.prop) { |
|
// Preface gives us something to blacklist in warning module |
|
("production" !== process.env.NODE_ENV ? warning( |
|
false, |
|
'Failed Composite propType: %s%s', |
|
error.message, |
|
addendum |
|
) : null); |
|
} else { |
|
("production" !== process.env.NODE_ENV ? warning( |
|
false, |
|
'Failed Context Types: %s%s', |
|
error.message, |
|
addendum |
|
) : null); |
|
} |
|
} |
|
} |
|
} |
|
}, |
|
|
|
receiveComponent: function(nextElement, transaction, nextContext) { |
|
var prevElement = this._currentElement; |
|
var prevContext = this._context; |
|
|
|
this._pendingElement = null; |
|
|
|
this.updateComponent( |
|
transaction, |
|
prevElement, |
|
nextElement, |
|
prevContext, |
|
nextContext |
|
); |
|
}, |
|
|
|
/** |
|
* If any of `_pendingElement`, `_pendingStateQueue`, or `_pendingForceUpdate` |
|
* is set, update the component. |
|
* |
|
* @param {ReactReconcileTransaction} transaction |
|
* @internal |
|
*/ |
|
performUpdateIfNecessary: function(transaction) { |
|
if (this._pendingElement != null) { |
|
ReactReconciler.receiveComponent( |
|
this, |
|
this._pendingElement || this._currentElement, |
|
transaction, |
|
this._context |
|
); |
|
} |
|
|
|
if (this._pendingStateQueue !== null || this._pendingForceUpdate) { |
|
if ("production" !== process.env.NODE_ENV) { |
|
ReactElementValidator.checkAndWarnForMutatedProps( |
|
this._currentElement |
|
); |
|
} |
|
|
|
this.updateComponent( |
|
transaction, |
|
this._currentElement, |
|
this._currentElement, |
|
this._context, |
|
this._context |
|
); |
|
} |
|
}, |
|
|
|
/** |
|
* Compare two contexts, warning if they are different |
|
* TODO: Remove this check when owner-context is removed |
|
*/ |
|
_warnIfContextsDiffer: function(ownerBasedContext, parentBasedContext) { |
|
ownerBasedContext = this._maskContext(ownerBasedContext); |
|
parentBasedContext = this._maskContext(parentBasedContext); |
|
var parentKeys = Object.keys(parentBasedContext).sort(); |
|
var displayName = this.getName() || 'ReactCompositeComponent'; |
|
for (var i = 0; i < parentKeys.length; i++) { |
|
var key = parentKeys[i]; |
|
("production" !== process.env.NODE_ENV ? warning( |
|
ownerBasedContext[key] === parentBasedContext[key], |
|
'owner-based and parent-based contexts differ ' + |
|
'(values: `%s` vs `%s`) for key (%s) while mounting %s ' + |
|
'(see: http://fb.me/react-context-by-parent)', |
|
ownerBasedContext[key], |
|
parentBasedContext[key], |
|
key, |
|
displayName |
|
) : null); |
|
} |
|
}, |
|
|
|
/** |
|
* Perform an update to a mounted component. The componentWillReceiveProps and |
|
* shouldComponentUpdate methods are called, then (assuming the update isn't |
|
* skipped) the remaining update lifecycle methods are called and the DOM |
|
* representation is updated. |
|
* |
|
* By default, this implements React's rendering and reconciliation algorithm. |
|
* Sophisticated clients may wish to override this. |
|
* |
|
* @param {ReactReconcileTransaction} transaction |
|
* @param {ReactElement} prevParentElement |
|
* @param {ReactElement} nextParentElement |
|
* @internal |
|
* @overridable |
|
*/ |
|
updateComponent: function( |
|
transaction, |
|
prevParentElement, |
|
nextParentElement, |
|
prevUnmaskedContext, |
|
nextUnmaskedContext |
|
) { |
|
var inst = this._instance; |
|
|
|
var nextContext = inst.context; |
|
var nextProps = inst.props; |
|
|
|
// Distinguish between a props update versus a simple state update |
|
if (prevParentElement !== nextParentElement) { |
|
nextContext = this._processContext(nextParentElement._context); |
|
nextProps = this._processProps(nextParentElement.props); |
|
|
|
if ("production" !== process.env.NODE_ENV) { |
|
if (nextUnmaskedContext != null) { |
|
this._warnIfContextsDiffer( |
|
nextParentElement._context, |
|
nextUnmaskedContext |
|
); |
|
} |
|
} |
|
|
|
// An update here will schedule an update but immediately set |
|
// _pendingStateQueue which will ensure that any state updates gets |
|
// immediately reconciled instead of waiting for the next batch. |
|
|
|
if (inst.componentWillReceiveProps) { |
|
inst.componentWillReceiveProps(nextProps, nextContext); |
|
} |
|
} |
|
|
|
var nextState = this._processPendingState(nextProps, nextContext); |
|
|
|
var shouldUpdate = |
|
this._pendingForceUpdate || |
|
!inst.shouldComponentUpdate || |
|
inst.shouldComponentUpdate(nextProps, nextState, nextContext); |
|
|
|
if ("production" !== process.env.NODE_ENV) { |
|
("production" !== process.env.NODE_ENV ? warning( |
|
typeof shouldUpdate !== 'undefined', |
|
'%s.shouldComponentUpdate(): Returned undefined instead of a ' + |
|
'boolean value. Make sure to return true or false.', |
|
this.getName() || 'ReactCompositeComponent' |
|
) : null); |
|
} |
|
|
|
if (shouldUpdate) { |
|
this._pendingForceUpdate = false; |
|
// Will set `this.props`, `this.state` and `this.context`. |
|
this._performComponentUpdate( |
|
nextParentElement, |
|
nextProps, |
|
nextState, |
|
nextContext, |
|
transaction, |
|
nextUnmaskedContext |
|
); |
|
} else { |
|
// If it's determined that a component should not update, we still want |
|
// to set props and state but we shortcut the rest of the update. |
|
this._currentElement = nextParentElement; |
|
this._context = nextUnmaskedContext; |
|
inst.props = nextProps; |
|
inst.state = nextState; |
|
inst.context = nextContext; |
|
} |
|
}, |
|
|
|
_processPendingState: function(props, context) { |
|
var inst = this._instance; |
|
var queue = this._pendingStateQueue; |
|
var replace = this._pendingReplaceState; |
|
this._pendingReplaceState = false; |
|
this._pendingStateQueue = null; |
|
|
|
if (!queue) { |
|
return inst.state; |
|
} |
|
|
|
var nextState = assign({}, replace ? queue[0] : inst.state); |
|
for (var i = replace ? 1 : 0; i < queue.length; i++) { |
|
var partial = queue[i]; |
|
assign( |
|
nextState, |
|
typeof partial === 'function' ? |
|
partial.call(inst, nextState, props, context) : |
|
partial |
|
); |
|
} |
|
|
|
return nextState; |
|
}, |
|
|
|
/** |
|
* 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 |
|
* @param {?object} unmaskedContext |
|
* @private |
|
*/ |
|
_performComponentUpdate: function( |
|
nextElement, |
|
nextProps, |
|
nextState, |
|
nextContext, |
|
transaction, |
|
unmaskedContext |
|
) { |
|
var inst = this._instance; |
|
|
|
var prevProps = inst.props; |
|
var prevState = inst.state; |
|
var prevContext = inst.context; |
|
|
|
if (inst.componentWillUpdate) { |
|
inst.componentWillUpdate(nextProps, nextState, nextContext); |
|
} |
|
|
|
this._currentElement = nextElement; |
|
this._context = unmaskedContext; |
|
inst.props = nextProps; |
|
inst.state = nextState; |
|
inst.context = nextContext; |
|
|
|
this._updateRenderedComponent(transaction, unmaskedContext); |
|
|
|
if (inst.componentDidUpdate) { |
|
transaction.getReactMountReady().enqueue( |
|
inst.componentDidUpdate.bind(inst, prevProps, prevState, prevContext), |
|
inst |
|
); |
|
} |
|
}, |
|
|
|
/** |
|
* Call the component's `render` method and update the DOM accordingly. |
|
* |
|
* @param {ReactReconcileTransaction} transaction |
|
* @internal |
|
*/ |
|
_updateRenderedComponent: function(transaction, context) { |
|
var prevComponentInstance = this._renderedComponent; |
|
var prevRenderedElement = prevComponentInstance._currentElement; |
|
var nextRenderedElement = this._renderValidatedComponent(); |
|
if (shouldUpdateReactComponent(prevRenderedElement, nextRenderedElement)) { |
|
ReactReconciler.receiveComponent( |
|
prevComponentInstance, |
|
nextRenderedElement, |
|
transaction, |
|
this._processChildContext(context) |
|
); |
|
} else { |
|
// These two IDs are actually the same! But nothing should rely on that. |
|
var thisID = this._rootNodeID; |
|
var prevComponentID = prevComponentInstance._rootNodeID; |
|
ReactReconciler.unmountComponent(prevComponentInstance); |
|
|
|
this._renderedComponent = this._instantiateReactComponent( |
|
nextRenderedElement, |
|
this._currentElement.type |
|
); |
|
var nextMarkup = ReactReconciler.mountComponent( |
|
this._renderedComponent, |
|
thisID, |
|
transaction, |
|
this._processChildContext(context) |
|
); |
|
this._replaceNodeWithMarkupByID(prevComponentID, nextMarkup); |
|
} |
|
}, |
|
|
|
/** |
|
* @protected |
|
*/ |
|
_replaceNodeWithMarkupByID: function(prevComponentID, nextMarkup) { |
|
ReactComponentEnvironment.replaceNodeWithMarkupByID( |
|
prevComponentID, |
|
nextMarkup |
|
); |
|
}, |
|
|
|
/** |
|
* @protected |
|
*/ |
|
_renderValidatedComponentWithoutOwnerOrContext: function() { |
|
var inst = this._instance; |
|
var renderedComponent = inst.render(); |
|
if ("production" !== process.env.NODE_ENV) { |
|
// We allow auto-mocks to proceed as if they're returning null. |
|
if (typeof renderedComponent === 'undefined' && |
|
inst.render._isMockFunction) { |
|
// This is probably bad practice. Consider warning here and |
|
// deprecating this convenience. |
|
renderedComponent = null; |
|
} |
|
} |
|
|
|
return renderedComponent; |
|
}, |
|
|
|
/** |
|
* @private |
|
*/ |
|
_renderValidatedComponent: function() { |
|
var renderedComponent; |
|
var previousContext = ReactContext.current; |
|
ReactContext.current = this._processChildContext( |
|
this._currentElement._context |
|
); |
|
ReactCurrentOwner.current = this; |
|
try { |
|
renderedComponent = |
|
this._renderValidatedComponentWithoutOwnerOrContext(); |
|
} finally { |
|
ReactContext.current = previousContext; |
|
ReactCurrentOwner.current = null; |
|
} |
|
("production" !== process.env.NODE_ENV ? invariant( |
|
// TODO: An `isValidNode` function would probably be more appropriate |
|
renderedComponent === null || renderedComponent === false || |
|
ReactElement.isValidElement(renderedComponent), |
|
'%s.render(): A valid ReactComponent must be returned. You may have ' + |
|
'returned undefined, an array or some other invalid object.', |
|
this.getName() || 'ReactCompositeComponent' |
|
) : invariant(// TODO: An `isValidNode` function would probably be more appropriate |
|
renderedComponent === null || renderedComponent === false || |
|
ReactElement.isValidElement(renderedComponent))); |
|
return renderedComponent; |
|
}, |
|
|
|
/** |
|
* Lazily allocates the refs object and stores `component` as `ref`. |
|
* |
|
* @param {string} ref Reference name. |
|
* @param {component} component Component to store as `ref`. |
|
* @final |
|
* @private |
|
*/ |
|
attachRef: function(ref, component) { |
|
var inst = this.getPublicInstance(); |
|
var refs = inst.refs === emptyObject ? (inst.refs = {}) : inst.refs; |
|
refs[ref] = component.getPublicInstance(); |
|
}, |
|
|
|
/** |
|
* Detaches a reference name. |
|
* |
|
* @param {string} ref Name to dereference. |
|
* @final |
|
* @private |
|
*/ |
|
detachRef: function(ref) { |
|
var refs = this.getPublicInstance().refs; |
|
delete refs[ref]; |
|
}, |
|
|
|
/** |
|
* Get a text description of the component that can be used to identify it |
|
* in error messages. |
|
* @return {string} The name or null. |
|
* @internal |
|
*/ |
|
getName: function() { |
|
var type = this._currentElement.type; |
|
var constructor = this._instance && this._instance.constructor; |
|
return ( |
|
type.displayName || (constructor && constructor.displayName) || |
|
type.name || (constructor && constructor.name) || |
|
null |
|
); |
|
}, |
|
|
|
/** |
|
* Get the publicly accessible representation of this component - i.e. what |
|
* is exposed by refs and returned by React.render. Can be null for stateless |
|
* components. |
|
* |
|
* @return {ReactComponent} the public component instance. |
|
* @internal |
|
*/ |
|
getPublicInstance: function() { |
|
return this._instance; |
|
}, |
|
|
|
// Stub |
|
_instantiateReactComponent: null |
|
|
|
}; |
|
|
|
ReactPerf.measureMethods( |
|
ReactCompositeComponentMixin, |
|
'ReactCompositeComponent', |
|
{ |
|
mountComponent: 'mountComponent', |
|
updateComponent: 'updateComponent', |
|
_renderValidatedComponent: '_renderValidatedComponent' |
|
} |
|
); |
|
|
|
var ReactCompositeComponent = { |
|
|
|
Mixin: ReactCompositeComponentMixin |
|
|
|
}; |
|
|
|
module.exports = ReactCompositeComponent;
|
|
|