mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
* Cut out isomorphic dependencies from the renderers These files reaches into isomorphic files. The ReactElement functions are exposed on the React object anyway so I can just use those instead. I also found some files that are not shared that should be in renderers shared. * Found a few more shared dependencies renderSubtreeIntoContainer is only used by the DOM renderer. It's not an addon. ReactClass isn't needed as a dependency since injection doesn't happen anymore. * Use a shim file to load addons' dependencies on DOM By replacing this intermediate file we can do the lazy loading without needing any lazy requires. This set up works with ES modules. We could also replace the globalShim thing with aliased files instead for consistency. * Bundle DOM renderers into their individual UMD bundles Instead of exposing the entire DOM renderer on the react.js package, I only expose CurrentOwner and ComponentTreeDevtool which are currently the only two modules that share __state__ with the renderers. Then I package each renderer in its own package. That could allow us to drop more server dependencies from the client package. It will also allow us to ship fiber as a separate renderer. Unminified DEV after before react.js 123kb 696kb react-with-addons.js 227kb 774kb react-dom.js 668kb 1kb react-dom-server.js 638kb 1kb Minified PROD after before react.min.js 24kb 154kb react-with-addons.min.js 37kb 166kb react-dom.min.js 149kb 1kb react-dom-server.min.js 144kb 1kb The total size for react.min.js + react-dom.min.js is +19kb larger because of the overlap between them right now. I'd like to see what an optimizing compiler can do to this. Some of that is fbjs stuff. There shouldn't need to be that much overlap so that's something we can hunt. We should keep isomorphic absolutely minimal so there's no reason for other React clones not to use it. There will be less overlap with Fiber. However, another strategy that we could do is package the isomorphic package into each renderer bundle and conditionally initialize it if it hasn't already been initialized. That way you only pay an overlap tax when there are two renderers on the page but not without it. It's also easier to just pull in one package. The downside is the versioning stuff that the separate npm package would solve. That applies to CDNs as well. ReactWithAddons is a bit weird because it is packaged into the isomorphic package but has a bunch of DOM dependencies. So we have to load them lazily since the DOM package gets initialized after.
1261 lines
36 KiB
JavaScript
1261 lines
36 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.
|
|
*
|
|
* @providesModule ReactCompositeComponent
|
|
*/
|
|
|
|
'use strict';
|
|
|
|
var ReactComponentEnvironment = require('ReactComponentEnvironment');
|
|
var ReactCurrentOwner = require('ReactCurrentOwner');
|
|
var ReactElement = require('ReactElement');
|
|
var ReactErrorUtils = require('ReactErrorUtils');
|
|
var ReactInstanceMap = require('ReactInstanceMap');
|
|
var ReactInstrumentation = require('ReactInstrumentation');
|
|
var ReactNodeTypes = require('ReactNodeTypes');
|
|
var ReactReconciler = require('ReactReconciler');
|
|
|
|
if (__DEV__) {
|
|
var ReactPropTypeLocations = require('ReactPropTypeLocations');
|
|
var checkReactTypeSpec = require('checkReactTypeSpec');
|
|
}
|
|
|
|
var emptyObject = require('emptyObject');
|
|
var invariant = require('invariant');
|
|
var shallowEqual = require('shallowEqual');
|
|
var shouldUpdateReactComponent = require('shouldUpdateReactComponent');
|
|
var warning = require('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 (__DEV__) {
|
|
warning(
|
|
element === null || element === false || ReactElement.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'
|
|
);
|
|
warning(
|
|
!Component.childContextTypes,
|
|
'%s(...): childContextTypes cannot be defined on a functional component.',
|
|
Component.displayName || Component.name || 'Component'
|
|
);
|
|
}
|
|
}
|
|
|
|
function invokeComponentDidMountWithTimer() {
|
|
var publicInstance = this._instance;
|
|
if (this._debugID !== 0) {
|
|
ReactInstrumentation.debugTool.onBeginLifeCycleTimer(
|
|
this._debugID,
|
|
'componentDidMount'
|
|
);
|
|
}
|
|
publicInstance.componentDidMount();
|
|
if (this._debugID !== 0) {
|
|
ReactInstrumentation.debugTool.onEndLifeCycleTimer(
|
|
this._debugID,
|
|
'componentDidMount'
|
|
);
|
|
}
|
|
}
|
|
|
|
function invokeComponentDidUpdateWithTimer(prevProps, prevState, prevContext) {
|
|
var publicInstance = this._instance;
|
|
if (this._debugID !== 0) {
|
|
ReactInstrumentation.debugTool.onBeginLifeCycleTimer(
|
|
this._debugID,
|
|
'componentDidUpdate'
|
|
);
|
|
}
|
|
publicInstance.componentDidUpdate(prevProps, prevState, prevContext);
|
|
if (this._debugID !== 0) {
|
|
ReactInstrumentation.debugTool.onEndLifeCycleTimer(
|
|
this._debugID,
|
|
'componentDidUpdate'
|
|
);
|
|
}
|
|
}
|
|
|
|
function shouldConstruct(Component) {
|
|
return !!(Component.prototype && Component.prototype.isReactComponent);
|
|
}
|
|
|
|
function isPureComponent(Component) {
|
|
return !!(Component.prototype && Component.prototype.isPureReactComponent);
|
|
}
|
|
|
|
/**
|
|
* ------------------ 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._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 (__DEV__) {
|
|
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
|
|
) {
|
|
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);
|
|
invariant(
|
|
inst === null ||
|
|
inst === false ||
|
|
ReactElement.isValidElement(inst),
|
|
'%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'
|
|
);
|
|
inst = new StatelessComponent(Component);
|
|
this._compositeType = CompositeTypes.StatelessFunctional;
|
|
} else {
|
|
if (isPureComponent(Component)) {
|
|
this._compositeType = CompositeTypes.PureClass;
|
|
} else {
|
|
this._compositeType = CompositeTypes.ImpureClass;
|
|
}
|
|
}
|
|
|
|
if (__DEV__) {
|
|
// This will throw later in _renderValidatedComponent, but add an early
|
|
// warning now to help debugging
|
|
if (inst.render == null) {
|
|
warning(
|
|
false,
|
|
'%s(...): No `render` method found on the returned component ' +
|
|
'instance: you may have forgotten to define `render`.',
|
|
Component.displayName || Component.name || 'Component'
|
|
);
|
|
}
|
|
|
|
var propsMutated = inst.props !== publicProps;
|
|
var componentName =
|
|
Component.displayName || Component.name || 'Component';
|
|
|
|
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
|
|
);
|
|
}
|
|
|
|
// 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 (__DEV__) {
|
|
// 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.
|
|
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'
|
|
);
|
|
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'
|
|
);
|
|
warning(
|
|
!inst.propTypes,
|
|
'propTypes was defined as an instance property on %s. Use a static ' +
|
|
'property to define propTypes instead.',
|
|
this.getName() || 'a component'
|
|
);
|
|
warning(
|
|
!inst.contextTypes,
|
|
'contextTypes was defined as an instance property on %s. Use a ' +
|
|
'static property to define contextTypes instead.',
|
|
this.getName() || 'a component'
|
|
);
|
|
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')
|
|
);
|
|
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'
|
|
);
|
|
warning(
|
|
typeof inst.componentWillRecieveProps !== 'function',
|
|
'%s has a method called ' +
|
|
'componentWillRecieveProps(). Did you mean componentWillReceiveProps()?',
|
|
(this.getName() || 'A component')
|
|
);
|
|
}
|
|
|
|
var initialState = inst.state;
|
|
if (initialState === undefined) {
|
|
inst.state = initialState = null;
|
|
}
|
|
invariant(
|
|
typeof initialState === 'object' && !Array.isArray(initialState),
|
|
'%s.state: must be set to an object or null',
|
|
this.getName() || 'ReactCompositeComponent'
|
|
);
|
|
|
|
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 (__DEV__) {
|
|
transaction.getReactMountReady().enqueue(invokeComponentDidMountWithTimer, this);
|
|
} else {
|
|
transaction.getReactMountReady().enqueue(inst.componentDidMount, inst);
|
|
}
|
|
}
|
|
|
|
if (__DEV__) {
|
|
if (this._debugID) {
|
|
var callback = (component) => ReactInstrumentation.debugTool.onComponentHasMounted(this._debugID);
|
|
transaction.getReactMountReady().enqueue(callback, this);
|
|
}
|
|
}
|
|
|
|
return markup;
|
|
},
|
|
|
|
_constructComponent: function(
|
|
doConstruct,
|
|
publicProps,
|
|
publicContext,
|
|
updateQueue
|
|
) {
|
|
if (__DEV__) {
|
|
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;
|
|
var instanceOrElement;
|
|
if (doConstruct) {
|
|
if (__DEV__) {
|
|
if (this._debugID !== 0) {
|
|
ReactInstrumentation.debugTool.onBeginLifeCycleTimer(
|
|
this._debugID,
|
|
'ctor'
|
|
);
|
|
}
|
|
}
|
|
instanceOrElement = new Component(publicProps, publicContext, updateQueue);
|
|
if (__DEV__) {
|
|
if (this._debugID !== 0) {
|
|
ReactInstrumentation.debugTool.onEndLifeCycleTimer(
|
|
this._debugID,
|
|
'ctor'
|
|
);
|
|
}
|
|
}
|
|
} else {
|
|
// 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 (__DEV__) {
|
|
if (this._debugID !== 0) {
|
|
ReactInstrumentation.debugTool.onBeginLifeCycleTimer(
|
|
this._debugID,
|
|
'render'
|
|
);
|
|
}
|
|
}
|
|
instanceOrElement = Component(publicProps, publicContext, updateQueue);
|
|
if (__DEV__) {
|
|
if (this._debugID !== 0) {
|
|
ReactInstrumentation.debugTool.onEndLifeCycleTimer(
|
|
this._debugID,
|
|
'render'
|
|
);
|
|
}
|
|
}
|
|
}
|
|
return instanceOrElement;
|
|
},
|
|
|
|
performInitialMountWithErrorHandling: function(
|
|
renderedElement,
|
|
hostParent,
|
|
hostContainerInfo,
|
|
transaction,
|
|
context
|
|
) {
|
|
var markup;
|
|
var checkpoint = transaction.checkpoint();
|
|
try {
|
|
markup = this.performInitialMount(renderedElement, hostParent, hostContainerInfo, transaction, context);
|
|
} catch (e) {
|
|
if (__DEV__) {
|
|
if (this._debugID !== 0) {
|
|
ReactInstrumentation.debugTool.onError();
|
|
}
|
|
}
|
|
// 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;
|
|
if (inst.componentWillMount) {
|
|
if (__DEV__) {
|
|
if (this._debugID !== 0) {
|
|
ReactInstrumentation.debugTool.onBeginLifeCycleTimer(
|
|
this._debugID,
|
|
'componentWillMount'
|
|
);
|
|
}
|
|
}
|
|
inst.componentWillMount();
|
|
if (__DEV__) {
|
|
if (this._debugID !== 0) {
|
|
ReactInstrumentation.debugTool.onEndLifeCycleTimer(
|
|
this._debugID,
|
|
'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;
|
|
if (__DEV__) {
|
|
if (child._debugID !== 0 && this._debugID !== 0) {
|
|
ReactInstrumentation.debugTool.onSetParent(
|
|
child._debugID,
|
|
this._debugID
|
|
);
|
|
}
|
|
}
|
|
|
|
var markup = ReactReconciler.mountComponent(
|
|
child,
|
|
transaction,
|
|
hostParent,
|
|
hostContainerInfo,
|
|
this._processChildContext(context)
|
|
);
|
|
|
|
if (__DEV__) {
|
|
if (this._debugID !== 0) {
|
|
ReactInstrumentation.debugTool.onSetChildren(
|
|
this._debugID,
|
|
child._debugID !== 0 ? [child._debugID] : []
|
|
);
|
|
}
|
|
}
|
|
|
|
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 (__DEV__) {
|
|
if (this._debugID !== 0) {
|
|
ReactInstrumentation.debugTool.onBeginLifeCycleTimer(
|
|
this._debugID,
|
|
'componentWillUnmount'
|
|
);
|
|
}
|
|
}
|
|
if (safely) {
|
|
var name = this.getName() + '.componentWillUnmount()';
|
|
ReactErrorUtils.invokeGuardedCallback(name, inst.componentWillUnmount.bind(inst));
|
|
} else {
|
|
inst.componentWillUnmount();
|
|
}
|
|
if (__DEV__) {
|
|
if (this._debugID !== 0) {
|
|
ReactInstrumentation.debugTool.onEndLifeCycleTimer(
|
|
this._debugID,
|
|
'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 = null;
|
|
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 (__DEV__) {
|
|
var Component = this._currentElement.type;
|
|
if (Component.contextTypes) {
|
|
this._checkContextTypes(
|
|
Component.contextTypes,
|
|
maskedContext,
|
|
ReactPropTypeLocations.context
|
|
);
|
|
}
|
|
}
|
|
return maskedContext;
|
|
},
|
|
|
|
/**
|
|
* @param {object} currentContext
|
|
* @return {object}
|
|
* @private
|
|
*/
|
|
_processChildContext: function(currentContext) {
|
|
var Component = this._currentElement.type;
|
|
var inst = this._instance;
|
|
if (__DEV__) {
|
|
ReactInstrumentation.debugTool.onBeginProcessingChildContext();
|
|
}
|
|
var childContext = inst.getChildContext && inst.getChildContext();
|
|
if (__DEV__) {
|
|
ReactInstrumentation.debugTool.onEndProcessingChildContext();
|
|
}
|
|
if (childContext) {
|
|
invariant(
|
|
typeof Component.childContextTypes === 'object',
|
|
'%s.getChildContext(): childContextTypes must be defined in order to ' +
|
|
'use getChildContext().',
|
|
this.getName() || 'ReactCompositeComponent'
|
|
);
|
|
if (__DEV__) {
|
|
this._checkContextTypes(
|
|
Component.childContextTypes,
|
|
childContext,
|
|
ReactPropTypeLocations.childContext
|
|
);
|
|
}
|
|
for (var name in childContext) {
|
|
invariant(
|
|
name in Component.childContextTypes,
|
|
'%s.getChildContext(): key "%s" is not defined in childContextTypes.',
|
|
this.getName() || 'ReactCompositeComponent',
|
|
name
|
|
);
|
|
}
|
|
return Object.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 (__DEV__) {
|
|
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;
|
|
invariant(
|
|
inst != null,
|
|
'Attempted to update component `%s` that has already been unmounted ' +
|
|
'(or failed to mount).',
|
|
this.getName() || 'ReactCompositeComponent'
|
|
);
|
|
|
|
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 (__DEV__) {
|
|
if (this._debugID !== 0) {
|
|
ReactInstrumentation.debugTool.onBeginLifeCycleTimer(
|
|
this._debugID,
|
|
'componentWillReceiveProps'
|
|
);
|
|
}
|
|
}
|
|
inst.componentWillReceiveProps(nextProps, nextContext);
|
|
if (__DEV__) {
|
|
if (this._debugID !== 0) {
|
|
ReactInstrumentation.debugTool.onEndLifeCycleTimer(
|
|
this._debugID,
|
|
'componentWillReceiveProps'
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
var nextState = this._processPendingState(nextProps, nextContext);
|
|
var shouldUpdate = true;
|
|
|
|
if (!this._pendingForceUpdate) {
|
|
if (inst.shouldComponentUpdate) {
|
|
if (__DEV__) {
|
|
if (this._debugID !== 0) {
|
|
ReactInstrumentation.debugTool.onBeginLifeCycleTimer(
|
|
this._debugID,
|
|
'shouldComponentUpdate'
|
|
);
|
|
}
|
|
}
|
|
shouldUpdate = inst.shouldComponentUpdate(nextProps, nextState, nextContext);
|
|
if (__DEV__) {
|
|
if (this._debugID !== 0) {
|
|
ReactInstrumentation.debugTool.onEndLifeCycleTimer(
|
|
this._debugID,
|
|
'shouldComponentUpdate'
|
|
);
|
|
}
|
|
}
|
|
} else {
|
|
if (this._compositeType === CompositeTypes.PureClass) {
|
|
shouldUpdate =
|
|
!shallowEqual(prevProps, nextProps) ||
|
|
!shallowEqual(inst.state, nextState);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (__DEV__) {
|
|
warning(
|
|
shouldUpdate !== undefined,
|
|
'%s.shouldComponentUpdate(): Returned undefined instead of a ' +
|
|
'boolean value. Make sure to return true or false.',
|
|
this.getName() || 'ReactCompositeComponent'
|
|
);
|
|
}
|
|
|
|
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 = Object.assign({}, replace ? queue[0] : inst.state);
|
|
for (var i = replace ? 1 : 0; i < queue.length; i++) {
|
|
var partial = queue[i];
|
|
Object.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 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 (__DEV__) {
|
|
if (this._debugID !== 0) {
|
|
ReactInstrumentation.debugTool.onBeginLifeCycleTimer(
|
|
this._debugID,
|
|
'componentWillUpdate'
|
|
);
|
|
}
|
|
}
|
|
inst.componentWillUpdate(nextProps, nextState, nextContext);
|
|
if (__DEV__) {
|
|
if (this._debugID !== 0) {
|
|
ReactInstrumentation.debugTool.onEndLifeCycleTimer(
|
|
this._debugID,
|
|
'componentWillUpdate'
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
this._currentElement = nextElement;
|
|
this._context = unmaskedContext;
|
|
inst.props = nextProps;
|
|
inst.state = nextState;
|
|
inst.context = nextContext;
|
|
|
|
this._updateRenderedComponent(transaction, unmaskedContext);
|
|
|
|
if (hasComponentDidUpdate) {
|
|
if (__DEV__) {
|
|
transaction.getReactMountReady().enqueue(
|
|
invokeComponentDidUpdateWithTimer.bind(this, prevProps, prevState, prevContext),
|
|
this
|
|
);
|
|
} else {
|
|
transaction.getReactMountReady().enqueue(
|
|
inst.componentDidUpdate.bind(inst, prevProps, prevState, prevContext),
|
|
inst
|
|
);
|
|
}
|
|
}
|
|
|
|
if (__DEV__) {
|
|
if (this._debugID) {
|
|
var callback = () => ReactInstrumentation.debugTool.onComponentHasUpdated(this._debugID);
|
|
transaction.getReactMountReady().enqueue(callback, this);
|
|
}
|
|
}
|
|
},
|
|
|
|
/**
|
|
* 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 {
|
|
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;
|
|
if (__DEV__) {
|
|
if (child._debugID !== 0 && this._debugID !== 0) {
|
|
ReactInstrumentation.debugTool.onSetParent(
|
|
child._debugID,
|
|
this._debugID
|
|
);
|
|
}
|
|
}
|
|
|
|
var nextMarkup = ReactReconciler.mountComponent(
|
|
child,
|
|
transaction,
|
|
this._hostParent,
|
|
this._hostContainerInfo,
|
|
this._processChildContext(context)
|
|
);
|
|
|
|
if (__DEV__) {
|
|
if (this._debugID !== 0) {
|
|
ReactInstrumentation.debugTool.onSetChildren(
|
|
this._debugID,
|
|
child._debugID !== 0 ? [child._debugID] : []
|
|
);
|
|
}
|
|
}
|
|
|
|
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;
|
|
|
|
if (__DEV__) {
|
|
if (this._debugID !== 0) {
|
|
ReactInstrumentation.debugTool.onBeginLifeCycleTimer(
|
|
this._debugID,
|
|
'render'
|
|
);
|
|
}
|
|
}
|
|
var renderedComponent = inst.render();
|
|
if (__DEV__) {
|
|
if (this._debugID !== 0) {
|
|
ReactInstrumentation.debugTool.onEndLifeCycleTimer(
|
|
this._debugID,
|
|
'render'
|
|
);
|
|
}
|
|
}
|
|
|
|
if (__DEV__) {
|
|
// We allow auto-mocks to proceed as if they're returning null.
|
|
if (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;
|
|
if (__DEV__ || this._compositeType !== CompositeTypes.StatelessFunctional) {
|
|
ReactCurrentOwner.current = this;
|
|
try {
|
|
renderedComponent =
|
|
this._renderValidatedComponentWithoutOwnerOrContext();
|
|
} finally {
|
|
ReactCurrentOwner.current = null;
|
|
}
|
|
} else {
|
|
renderedComponent =
|
|
this._renderValidatedComponentWithoutOwnerOrContext();
|
|
}
|
|
invariant(
|
|
// TODO: An `isValidNode` function would probably be more appropriate
|
|
renderedComponent === null || renderedComponent === false ||
|
|
ReactElement.isValidElement(renderedComponent),
|
|
'%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'
|
|
);
|
|
|
|
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();
|
|
invariant(inst != null, 'Stateless function components cannot have refs.');
|
|
var publicComponentInstance = component.getPublicInstance();
|
|
if (__DEV__) {
|
|
var componentName = component && component.getName ?
|
|
component.getName() : 'a component';
|
|
warning(publicComponentInstance != null,
|
|
'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()
|
|
);
|
|
}
|
|
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,
|
|
|
|
};
|
|
|
|
var ReactCompositeComponent = {
|
|
|
|
Mixin: ReactCompositeComponentMixin,
|
|
|
|
};
|
|
|
|
module.exports = ReactCompositeComponent;
|