899 lines
34 KiB
JavaScript
899 lines
34 KiB
JavaScript
/**
|
|
* Copyright 2013-present, 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.
|
|
*
|
|
*/
|
|
|
|
'use strict';
|
|
|
|
var _prodInvariant = require('./reactProdInvariant'),
|
|
_assign = require('object-assign');
|
|
|
|
var React = require('react/lib/React');
|
|
var ReactComponentEnvironment = require('./ReactComponentEnvironment');
|
|
var ReactCurrentOwner = require('react/lib/ReactCurrentOwner');
|
|
var ReactErrorUtils = require('./ReactErrorUtils');
|
|
var ReactInstanceMap = require('./ReactInstanceMap');
|
|
var ReactInstrumentation = require('./ReactInstrumentation');
|
|
var ReactNodeTypes = require('./ReactNodeTypes');
|
|
var ReactReconciler = require('./ReactReconciler');
|
|
|
|
if (process.env.NODE_ENV !== 'production') {
|
|
var checkReactTypeSpec = require('./checkReactTypeSpec');
|
|
}
|
|
|
|
var emptyObject = require('fbjs/lib/emptyObject');
|
|
var invariant = require('fbjs/lib/invariant');
|
|
var shallowEqual = require('fbjs/lib/shallowEqual');
|
|
var shouldUpdateReactComponent = require('./shouldUpdateReactComponent');
|
|
var warning = require('fbjs/lib/warning');
|
|
|
|
var CompositeTypes = {
|
|
ImpureClass: 0,
|
|
PureClass: 1,
|
|
StatelessFunctional: 2
|
|
};
|
|
|
|
function StatelessComponent(Component) {}
|
|
StatelessComponent.prototype.render = function () {
|
|
var Component = ReactInstanceMap.get(this)._currentElement.type;
|
|
var element = Component(this.props, this.context, this.updater);
|
|
warnIfInvalidElement(Component, element);
|
|
return element;
|
|
};
|
|
|
|
function warnIfInvalidElement(Component, element) {
|
|
if (process.env.NODE_ENV !== 'production') {
|
|
process.env.NODE_ENV !== 'production' ? warning(element === null || element === false || React.isValidElement(element), '%s(...): A valid React element (or null) must be returned. You may have ' + 'returned undefined, an array or some other invalid object.', Component.displayName || Component.name || 'Component') : void 0;
|
|
process.env.NODE_ENV !== 'production' ? warning(!Component.childContextTypes, '%s(...): childContextTypes cannot be defined on a functional component.', Component.displayName || Component.name || 'Component') : void 0;
|
|
}
|
|
}
|
|
|
|
function shouldConstruct(Component) {
|
|
return !!(Component.prototype && Component.prototype.isReactComponent);
|
|
}
|
|
|
|
function isPureComponent(Component) {
|
|
return !!(Component.prototype && Component.prototype.isPureReactComponent);
|
|
}
|
|
|
|
// Separated into a function to contain deoptimizations caused by try/finally.
|
|
function measureLifeCyclePerf(fn, debugID, timerType) {
|
|
if (debugID === 0) {
|
|
// Top-level wrappers (see ReactMount) and empty components (see
|
|
// ReactDOMEmptyComponent) are invisible to hooks and devtools.
|
|
// Both are implementation details that should go away in the future.
|
|
return fn();
|
|
}
|
|
|
|
ReactInstrumentation.debugTool.onBeginLifeCycleTimer(debugID, timerType);
|
|
try {
|
|
return fn();
|
|
} finally {
|
|
ReactInstrumentation.debugTool.onEndLifeCycleTimer(debugID, timerType);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* ------------------ 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 ReactCompositeComponent = {
|
|
/**
|
|
* Base constructor for all composite component.
|
|
*
|
|
* @param {ReactElement} element
|
|
* @final
|
|
* @internal
|
|
*/
|
|
construct: function (element) {
|
|
this._currentElement = element;
|
|
this._rootNodeID = 0;
|
|
this._compositeType = null;
|
|
this._instance = null;
|
|
this._hostParent = null;
|
|
this._hostContainerInfo = null;
|
|
|
|
// See ReactUpdateQueue
|
|
this._updateBatchNumber = null;
|
|
this._pendingElement = null;
|
|
this._pendingStateQueue = null;
|
|
this._pendingReplaceState = false;
|
|
this._pendingForceUpdate = false;
|
|
|
|
this._renderedNodeType = null;
|
|
this._renderedComponent = null;
|
|
this._context = null;
|
|
this._mountOrder = 0;
|
|
this._topLevelWrapper = null;
|
|
|
|
// See ReactUpdates and ReactUpdateQueue.
|
|
this._pendingCallbacks = null;
|
|
|
|
// ComponentWillUnmount shall only be called once
|
|
this._calledComponentWillUnmount = false;
|
|
|
|
if (process.env.NODE_ENV !== 'production') {
|
|
this._warnedAboutRefsInRender = false;
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Initializes the component, renders markup, and registers event listeners.
|
|
*
|
|
* @param {ReactReconcileTransaction|ReactServerRenderingTransaction} transaction
|
|
* @param {?object} hostParent
|
|
* @param {?object} hostContainerInfo
|
|
* @param {?object} context
|
|
* @return {?string} Rendered markup to be inserted into the DOM.
|
|
* @final
|
|
* @internal
|
|
*/
|
|
mountComponent: function (transaction, hostParent, hostContainerInfo, context) {
|
|
var _this = this;
|
|
|
|
this._context = context;
|
|
this._mountOrder = nextMountID++;
|
|
this._hostParent = hostParent;
|
|
this._hostContainerInfo = hostContainerInfo;
|
|
|
|
var publicProps = this._currentElement.props;
|
|
var publicContext = this._processContext(context);
|
|
|
|
var Component = this._currentElement.type;
|
|
|
|
var updateQueue = transaction.getUpdateQueue();
|
|
|
|
// Initialize the public class
|
|
var doConstruct = shouldConstruct(Component);
|
|
var inst = this._constructComponent(doConstruct, publicProps, publicContext, updateQueue);
|
|
var renderedElement;
|
|
|
|
// Support functional components
|
|
if (!doConstruct && (inst == null || inst.render == null)) {
|
|
renderedElement = inst;
|
|
warnIfInvalidElement(Component, renderedElement);
|
|
!(inst === null || inst === false || React.isValidElement(inst)) ? process.env.NODE_ENV !== 'production' ? invariant(false, '%s(...): A valid React element (or null) must be returned. You may have returned undefined, an array or some other invalid object.', Component.displayName || Component.name || 'Component') : _prodInvariant('105', Component.displayName || Component.name || 'Component') : void 0;
|
|
inst = new StatelessComponent(Component);
|
|
this._compositeType = CompositeTypes.StatelessFunctional;
|
|
} else {
|
|
if (isPureComponent(Component)) {
|
|
this._compositeType = CompositeTypes.PureClass;
|
|
} else {
|
|
this._compositeType = CompositeTypes.ImpureClass;
|
|
}
|
|
}
|
|
|
|
if (process.env.NODE_ENV !== 'production') {
|
|
// This will throw later in _renderValidatedComponent, but add an early
|
|
// warning now to help debugging
|
|
if (inst.render == null) {
|
|
process.env.NODE_ENV !== 'production' ? warning(false, '%s(...): No `render` method found on the returned component ' + 'instance: you may have forgotten to define `render`.', Component.displayName || Component.name || 'Component') : void 0;
|
|
}
|
|
|
|
var propsMutated = inst.props !== publicProps;
|
|
var componentName = Component.displayName || Component.name || 'Component';
|
|
|
|
process.env.NODE_ENV !== 'production' ? warning(inst.props === undefined || !propsMutated, '%s(...): When calling super() in `%s`, make sure to pass ' + "up the same props that your component's constructor was passed.", componentName, componentName) : void 0;
|
|
}
|
|
|
|
// 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;
|
|
inst.updater = updateQueue;
|
|
|
|
this._instance = inst;
|
|
|
|
// Store a reference from the instance back to the internal representation
|
|
ReactInstanceMap.set(inst, this);
|
|
|
|
if (process.env.NODE_ENV !== 'production') {
|
|
// 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.
|
|
process.env.NODE_ENV !== 'production' ? warning(!inst.getInitialState || inst.getInitialState.isReactClassApproved || inst.state, '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') : void 0;
|
|
process.env.NODE_ENV !== 'production' ? 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') : void 0;
|
|
process.env.NODE_ENV !== 'production' ? warning(!inst.propTypes, 'propTypes was defined as an instance property on %s. Use a static ' + 'property to define propTypes instead.', this.getName() || 'a component') : void 0;
|
|
process.env.NODE_ENV !== 'production' ? warning(!inst.contextTypes, 'contextTypes was defined as an instance property on %s. Use a ' + 'static property to define contextTypes instead.', this.getName() || 'a component') : void 0;
|
|
process.env.NODE_ENV !== 'production' ? 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') : void 0;
|
|
process.env.NODE_ENV !== 'production' ? warning(typeof inst.componentDidUnmount !== 'function', '%s has a method called ' + 'componentDidUnmount(). But there is no such lifecycle method. ' + 'Did you mean componentWillUnmount()?', this.getName() || 'A component') : void 0;
|
|
process.env.NODE_ENV !== 'production' ? warning(typeof inst.componentWillRecieveProps !== 'function', '%s has a method called ' + 'componentWillRecieveProps(). Did you mean componentWillReceiveProps()?', this.getName() || 'A component') : void 0;
|
|
}
|
|
|
|
var initialState = inst.state;
|
|
if (initialState === undefined) {
|
|
inst.state = initialState = null;
|
|
}
|
|
!(typeof initialState === 'object' && !Array.isArray(initialState)) ? process.env.NODE_ENV !== 'production' ? invariant(false, '%s.state: must be set to an object or null', this.getName() || 'ReactCompositeComponent') : _prodInvariant('106', this.getName() || 'ReactCompositeComponent') : void 0;
|
|
|
|
this._pendingStateQueue = null;
|
|
this._pendingReplaceState = false;
|
|
this._pendingForceUpdate = false;
|
|
|
|
var markup;
|
|
if (inst.unstable_handleError) {
|
|
markup = this.performInitialMountWithErrorHandling(renderedElement, hostParent, hostContainerInfo, transaction, context);
|
|
} else {
|
|
markup = this.performInitialMount(renderedElement, hostParent, hostContainerInfo, transaction, context);
|
|
}
|
|
|
|
if (inst.componentDidMount) {
|
|
if (process.env.NODE_ENV !== 'production') {
|
|
transaction.getReactMountReady().enqueue(function () {
|
|
measureLifeCyclePerf(function () {
|
|
return inst.componentDidMount();
|
|
}, _this._debugID, 'componentDidMount');
|
|
});
|
|
} else {
|
|
transaction.getReactMountReady().enqueue(inst.componentDidMount, inst);
|
|
}
|
|
}
|
|
|
|
return markup;
|
|
},
|
|
|
|
_constructComponent: function (doConstruct, publicProps, publicContext, updateQueue) {
|
|
if (process.env.NODE_ENV !== 'production') {
|
|
ReactCurrentOwner.current = this;
|
|
try {
|
|
return this._constructComponentWithoutOwner(doConstruct, publicProps, publicContext, updateQueue);
|
|
} finally {
|
|
ReactCurrentOwner.current = null;
|
|
}
|
|
} else {
|
|
return this._constructComponentWithoutOwner(doConstruct, publicProps, publicContext, updateQueue);
|
|
}
|
|
},
|
|
|
|
_constructComponentWithoutOwner: function (doConstruct, publicProps, publicContext, updateQueue) {
|
|
var Component = this._currentElement.type;
|
|
|
|
if (doConstruct) {
|
|
if (process.env.NODE_ENV !== 'production') {
|
|
return measureLifeCyclePerf(function () {
|
|
return new Component(publicProps, publicContext, updateQueue);
|
|
}, this._debugID, 'ctor');
|
|
} else {
|
|
return new Component(publicProps, publicContext, updateQueue);
|
|
}
|
|
}
|
|
|
|
// This can still be an instance in case of factory components
|
|
// but we'll count this as time spent rendering as the more common case.
|
|
if (process.env.NODE_ENV !== 'production') {
|
|
return measureLifeCyclePerf(function () {
|
|
return Component(publicProps, publicContext, updateQueue);
|
|
}, this._debugID, 'render');
|
|
} else {
|
|
return Component(publicProps, publicContext, updateQueue);
|
|
}
|
|
},
|
|
|
|
performInitialMountWithErrorHandling: function (renderedElement, hostParent, hostContainerInfo, transaction, context) {
|
|
var markup;
|
|
var checkpoint = transaction.checkpoint();
|
|
try {
|
|
markup = this.performInitialMount(renderedElement, hostParent, hostContainerInfo, transaction, context);
|
|
} catch (e) {
|
|
// Roll back to checkpoint, handle error (which may add items to the transaction), and take a new checkpoint
|
|
transaction.rollback(checkpoint);
|
|
this._instance.unstable_handleError(e);
|
|
if (this._pendingStateQueue) {
|
|
this._instance.state = this._processPendingState(this._instance.props, this._instance.context);
|
|
}
|
|
checkpoint = transaction.checkpoint();
|
|
|
|
this._renderedComponent.unmountComponent(true);
|
|
transaction.rollback(checkpoint);
|
|
|
|
// Try again - we've informed the component about the error, so they can render an error message this time.
|
|
// If this throws again, the error will bubble up (and can be caught by a higher error boundary).
|
|
markup = this.performInitialMount(renderedElement, hostParent, hostContainerInfo, transaction, context);
|
|
}
|
|
return markup;
|
|
},
|
|
|
|
performInitialMount: function (renderedElement, hostParent, hostContainerInfo, transaction, context) {
|
|
var inst = this._instance;
|
|
|
|
var debugID = 0;
|
|
if (process.env.NODE_ENV !== 'production') {
|
|
debugID = this._debugID;
|
|
}
|
|
|
|
if (inst.componentWillMount) {
|
|
if (process.env.NODE_ENV !== 'production') {
|
|
measureLifeCyclePerf(function () {
|
|
return inst.componentWillMount();
|
|
}, debugID, 'componentWillMount');
|
|
} else {
|
|
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);
|
|
}
|
|
}
|
|
|
|
// If not a stateless component, we now render
|
|
if (renderedElement === undefined) {
|
|
renderedElement = this._renderValidatedComponent();
|
|
}
|
|
|
|
var nodeType = ReactNodeTypes.getType(renderedElement);
|
|
this._renderedNodeType = nodeType;
|
|
var child = this._instantiateReactComponent(renderedElement, nodeType !== ReactNodeTypes.EMPTY /* shouldHaveDebugID */
|
|
);
|
|
this._renderedComponent = child;
|
|
|
|
var markup = ReactReconciler.mountComponent(child, transaction, hostParent, hostContainerInfo, this._processChildContext(context), debugID);
|
|
|
|
if (process.env.NODE_ENV !== 'production') {
|
|
if (debugID !== 0) {
|
|
var childDebugIDs = child._debugID !== 0 ? [child._debugID] : [];
|
|
ReactInstrumentation.debugTool.onSetChildren(debugID, childDebugIDs);
|
|
}
|
|
}
|
|
|
|
return markup;
|
|
},
|
|
|
|
getHostNode: function () {
|
|
return ReactReconciler.getHostNode(this._renderedComponent);
|
|
},
|
|
|
|
/**
|
|
* Releases any resources allocated by `mountComponent`.
|
|
*
|
|
* @final
|
|
* @internal
|
|
*/
|
|
unmountComponent: function (safely) {
|
|
if (!this._renderedComponent) {
|
|
return;
|
|
}
|
|
|
|
var inst = this._instance;
|
|
|
|
if (inst.componentWillUnmount && !inst._calledComponentWillUnmount) {
|
|
inst._calledComponentWillUnmount = true;
|
|
|
|
if (safely) {
|
|
var name = this.getName() + '.componentWillUnmount()';
|
|
ReactErrorUtils.invokeGuardedCallback(name, inst.componentWillUnmount.bind(inst));
|
|
} else {
|
|
if (process.env.NODE_ENV !== 'production') {
|
|
measureLifeCyclePerf(function () {
|
|
return inst.componentWillUnmount();
|
|
}, this._debugID, 'componentWillUnmount');
|
|
} else {
|
|
inst.componentWillUnmount();
|
|
}
|
|
}
|
|
}
|
|
|
|
if (this._renderedComponent) {
|
|
ReactReconciler.unmountComponent(this._renderedComponent, safely);
|
|
this._renderedNodeType = null;
|
|
this._renderedComponent = null;
|
|
this._instance = null;
|
|
}
|
|
|
|
// Reset pending fields
|
|
// Even if this component is scheduled for another update in ReactUpdates,
|
|
// it would still be ignored because these fields are reset.
|
|
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 = 0;
|
|
this._topLevelWrapper = 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;
|
|
},
|
|
|
|
/**
|
|
* Filters the context object to only contain keys specified in
|
|
* `contextTypes`
|
|
*
|
|
* @param {object} context
|
|
* @return {?object}
|
|
* @private
|
|
*/
|
|
_maskContext: function (context) {
|
|
var Component = this._currentElement.type;
|
|
var contextTypes = Component.contextTypes;
|
|
if (!contextTypes) {
|
|
return emptyObject;
|
|
}
|
|
var 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 (process.env.NODE_ENV !== 'production') {
|
|
var Component = this._currentElement.type;
|
|
if (Component.contextTypes) {
|
|
this._checkContextTypes(Component.contextTypes, maskedContext, 'context');
|
|
}
|
|
}
|
|
return maskedContext;
|
|
},
|
|
|
|
/**
|
|
* @param {object} currentContext
|
|
* @return {object}
|
|
* @private
|
|
*/
|
|
_processChildContext: function (currentContext) {
|
|
var Component = this._currentElement.type;
|
|
var inst = this._instance;
|
|
var childContext;
|
|
|
|
if (inst.getChildContext) {
|
|
if (process.env.NODE_ENV !== 'production') {
|
|
ReactInstrumentation.debugTool.onBeginProcessingChildContext();
|
|
try {
|
|
childContext = inst.getChildContext();
|
|
} finally {
|
|
ReactInstrumentation.debugTool.onEndProcessingChildContext();
|
|
}
|
|
} else {
|
|
childContext = inst.getChildContext();
|
|
}
|
|
}
|
|
|
|
if (childContext) {
|
|
!(typeof Component.childContextTypes === 'object') ? process.env.NODE_ENV !== 'production' ? invariant(false, '%s.getChildContext(): childContextTypes must be defined in order to use getChildContext().', this.getName() || 'ReactCompositeComponent') : _prodInvariant('107', this.getName() || 'ReactCompositeComponent') : void 0;
|
|
if (process.env.NODE_ENV !== 'production') {
|
|
this._checkContextTypes(Component.childContextTypes, childContext, 'child context');
|
|
}
|
|
for (var name in childContext) {
|
|
!(name in Component.childContextTypes) ? process.env.NODE_ENV !== 'production' ? invariant(false, '%s.getChildContext(): key "%s" is not defined in childContextTypes.', this.getName() || 'ReactCompositeComponent', name) : _prodInvariant('108', this.getName() || 'ReactCompositeComponent', name) : void 0;
|
|
}
|
|
return _assign({}, currentContext, childContext);
|
|
}
|
|
return currentContext;
|
|
},
|
|
|
|
/**
|
|
* Assert that the context types are valid
|
|
*
|
|
* @param {object} typeSpecs Map of context field to a ReactPropType
|
|
* @param {object} values Runtime values that need to be type-checked
|
|
* @param {string} location e.g. "prop", "context", "child context"
|
|
* @private
|
|
*/
|
|
_checkContextTypes: function (typeSpecs, values, location) {
|
|
if (process.env.NODE_ENV !== 'production') {
|
|
checkReactTypeSpec(typeSpecs, values, location, this.getName(), null, this._debugID);
|
|
}
|
|
},
|
|
|
|
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, transaction, this._context);
|
|
} else if (this._pendingStateQueue !== null || this._pendingForceUpdate) {
|
|
this.updateComponent(transaction, this._currentElement, this._currentElement, this._context, this._context);
|
|
} else {
|
|
this._updateBatchNumber = 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;
|
|
!(inst != null) ? process.env.NODE_ENV !== 'production' ? invariant(false, 'Attempted to update component `%s` that has already been unmounted (or failed to mount).', this.getName() || 'ReactCompositeComponent') : _prodInvariant('136', this.getName() || 'ReactCompositeComponent') : void 0;
|
|
|
|
var willReceive = false;
|
|
var nextContext;
|
|
|
|
// Determine if the context has changed or not
|
|
if (this._context === nextUnmaskedContext) {
|
|
nextContext = inst.context;
|
|
} else {
|
|
nextContext = this._processContext(nextUnmaskedContext);
|
|
willReceive = true;
|
|
}
|
|
|
|
var prevProps = prevParentElement.props;
|
|
var nextProps = nextParentElement.props;
|
|
|
|
// Not a simple state update but a props update
|
|
if (prevParentElement !== nextParentElement) {
|
|
willReceive = true;
|
|
}
|
|
|
|
// 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 (willReceive && inst.componentWillReceiveProps) {
|
|
if (process.env.NODE_ENV !== 'production') {
|
|
measureLifeCyclePerf(function () {
|
|
return inst.componentWillReceiveProps(nextProps, nextContext);
|
|
}, this._debugID, 'componentWillReceiveProps');
|
|
} else {
|
|
inst.componentWillReceiveProps(nextProps, nextContext);
|
|
}
|
|
}
|
|
|
|
var nextState = this._processPendingState(nextProps, nextContext);
|
|
var shouldUpdate = true;
|
|
|
|
if (!this._pendingForceUpdate) {
|
|
if (inst.shouldComponentUpdate) {
|
|
if (process.env.NODE_ENV !== 'production') {
|
|
shouldUpdate = measureLifeCyclePerf(function () {
|
|
return inst.shouldComponentUpdate(nextProps, nextState, nextContext);
|
|
}, this._debugID, 'shouldComponentUpdate');
|
|
} else {
|
|
shouldUpdate = inst.shouldComponentUpdate(nextProps, nextState, nextContext);
|
|
}
|
|
} else {
|
|
if (this._compositeType === CompositeTypes.PureClass) {
|
|
shouldUpdate = !shallowEqual(prevProps, nextProps) || !shallowEqual(inst.state, nextState);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (process.env.NODE_ENV !== 'production') {
|
|
process.env.NODE_ENV !== 'production' ? warning(shouldUpdate !== undefined, '%s.shouldComponentUpdate(): Returned undefined instead of a ' + 'boolean value. Make sure to return true or false.', this.getName() || 'ReactCompositeComponent') : void 0;
|
|
}
|
|
|
|
this._updateBatchNumber = 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;
|
|
}
|
|
|
|
if (replace && queue.length === 1) {
|
|
return queue[0];
|
|
}
|
|
|
|
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 _this2 = this;
|
|
|
|
var inst = this._instance;
|
|
|
|
var hasComponentDidUpdate = Boolean(inst.componentDidUpdate);
|
|
var prevProps;
|
|
var prevState;
|
|
var prevContext;
|
|
if (hasComponentDidUpdate) {
|
|
prevProps = inst.props;
|
|
prevState = inst.state;
|
|
prevContext = inst.context;
|
|
}
|
|
|
|
if (inst.componentWillUpdate) {
|
|
if (process.env.NODE_ENV !== 'production') {
|
|
measureLifeCyclePerf(function () {
|
|
return inst.componentWillUpdate(nextProps, nextState, nextContext);
|
|
}, this._debugID, 'componentWillUpdate');
|
|
} else {
|
|
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 (hasComponentDidUpdate) {
|
|
if (process.env.NODE_ENV !== 'production') {
|
|
transaction.getReactMountReady().enqueue(function () {
|
|
measureLifeCyclePerf(inst.componentDidUpdate.bind(inst, prevProps, prevState, prevContext), _this2._debugID, 'componentDidUpdate');
|
|
});
|
|
} else {
|
|
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();
|
|
|
|
var debugID = 0;
|
|
if (process.env.NODE_ENV !== 'production') {
|
|
debugID = this._debugID;
|
|
}
|
|
|
|
if (shouldUpdateReactComponent(prevRenderedElement, nextRenderedElement)) {
|
|
ReactReconciler.receiveComponent(prevComponentInstance, nextRenderedElement, transaction, this._processChildContext(context));
|
|
} else {
|
|
var oldHostNode = ReactReconciler.getHostNode(prevComponentInstance);
|
|
ReactReconciler.unmountComponent(prevComponentInstance, false);
|
|
|
|
var nodeType = ReactNodeTypes.getType(nextRenderedElement);
|
|
this._renderedNodeType = nodeType;
|
|
var child = this._instantiateReactComponent(nextRenderedElement, nodeType !== ReactNodeTypes.EMPTY /* shouldHaveDebugID */
|
|
);
|
|
this._renderedComponent = child;
|
|
|
|
var nextMarkup = ReactReconciler.mountComponent(child, transaction, this._hostParent, this._hostContainerInfo, this._processChildContext(context), debugID);
|
|
|
|
if (process.env.NODE_ENV !== 'production') {
|
|
if (debugID !== 0) {
|
|
var childDebugIDs = child._debugID !== 0 ? [child._debugID] : [];
|
|
ReactInstrumentation.debugTool.onSetChildren(debugID, childDebugIDs);
|
|
}
|
|
}
|
|
|
|
this._replaceNodeWithMarkup(oldHostNode, nextMarkup, prevComponentInstance);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Overridden in shallow rendering.
|
|
*
|
|
* @protected
|
|
*/
|
|
_replaceNodeWithMarkup: function (oldHostNode, nextMarkup, prevInstance) {
|
|
ReactComponentEnvironment.replaceNodeWithMarkup(oldHostNode, nextMarkup, prevInstance);
|
|
},
|
|
|
|
/**
|
|
* @protected
|
|
*/
|
|
_renderValidatedComponentWithoutOwnerOrContext: function () {
|
|
var inst = this._instance;
|
|
var renderedElement;
|
|
|
|
if (process.env.NODE_ENV !== 'production') {
|
|
renderedElement = measureLifeCyclePerf(function () {
|
|
return inst.render();
|
|
}, this._debugID, 'render');
|
|
} else {
|
|
renderedElement = inst.render();
|
|
}
|
|
|
|
if (process.env.NODE_ENV !== 'production') {
|
|
// We allow auto-mocks to proceed as if they're returning null.
|
|
if (renderedElement === undefined && inst.render._isMockFunction) {
|
|
// This is probably bad practice. Consider warning here and
|
|
// deprecating this convenience.
|
|
renderedElement = null;
|
|
}
|
|
}
|
|
|
|
return renderedElement;
|
|
},
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
_renderValidatedComponent: function () {
|
|
var renderedElement;
|
|
if (process.env.NODE_ENV !== 'production' || this._compositeType !== CompositeTypes.StatelessFunctional) {
|
|
ReactCurrentOwner.current = this;
|
|
try {
|
|
renderedElement = this._renderValidatedComponentWithoutOwnerOrContext();
|
|
} finally {
|
|
ReactCurrentOwner.current = null;
|
|
}
|
|
} else {
|
|
renderedElement = this._renderValidatedComponentWithoutOwnerOrContext();
|
|
}
|
|
!(
|
|
// TODO: An `isValidNode` function would probably be more appropriate
|
|
renderedElement === null || renderedElement === false || React.isValidElement(renderedElement)) ? process.env.NODE_ENV !== 'production' ? invariant(false, '%s.render(): A valid React element (or null) must be returned. You may have returned undefined, an array or some other invalid object.', this.getName() || 'ReactCompositeComponent') : _prodInvariant('109', this.getName() || 'ReactCompositeComponent') : void 0;
|
|
|
|
return renderedElement;
|
|
},
|
|
|
|
/**
|
|
* 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();
|
|
!(inst != null) ? process.env.NODE_ENV !== 'production' ? invariant(false, 'Stateless function components cannot have refs.') : _prodInvariant('110') : void 0;
|
|
var publicComponentInstance = component.getPublicInstance();
|
|
if (process.env.NODE_ENV !== 'production') {
|
|
var componentName = component && component.getName ? component.getName() : 'a component';
|
|
process.env.NODE_ENV !== 'production' ? warning(publicComponentInstance != null || component._compositeType !== CompositeTypes.StatelessFunctional, 'Stateless function components cannot be given refs ' + '(See ref "%s" in %s created by %s). ' + 'Attempts to access this ref will fail.', ref, componentName, this.getName()) : void 0;
|
|
}
|
|
var refs = inst.refs === emptyObject ? inst.refs = {} : inst.refs;
|
|
refs[ref] = publicComponentInstance;
|
|
},
|
|
|
|
/**
|
|
* 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 render. Can be null for stateless
|
|
* components.
|
|
*
|
|
* @return {ReactComponent} the public component instance.
|
|
* @internal
|
|
*/
|
|
getPublicInstance: function () {
|
|
var inst = this._instance;
|
|
if (this._compositeType === CompositeTypes.StatelessFunctional) {
|
|
return null;
|
|
}
|
|
return inst;
|
|
},
|
|
|
|
// Stub
|
|
_instantiateReactComponent: null
|
|
};
|
|
|
|
module.exports = ReactCompositeComponent; |