react / react-0.13.3 / examples / basic-commonjs / node_modules / react / lib / ReactCompositeComponent.js
81143 views/**1* Copyright 2013-2015, Facebook, Inc.2* All rights reserved.3*4* This source code is licensed under the BSD-style license found in the5* LICENSE file in the root directory of this source tree. An additional grant6* of patent rights can be found in the PATENTS file in the same directory.7*8* @providesModule ReactCompositeComponent9*/1011'use strict';1213var ReactComponentEnvironment = require("./ReactComponentEnvironment");14var ReactContext = require("./ReactContext");15var ReactCurrentOwner = require("./ReactCurrentOwner");16var ReactElement = require("./ReactElement");17var ReactElementValidator = require("./ReactElementValidator");18var ReactInstanceMap = require("./ReactInstanceMap");19var ReactLifeCycle = require("./ReactLifeCycle");20var ReactNativeComponent = require("./ReactNativeComponent");21var ReactPerf = require("./ReactPerf");22var ReactPropTypeLocations = require("./ReactPropTypeLocations");23var ReactPropTypeLocationNames = require("./ReactPropTypeLocationNames");24var ReactReconciler = require("./ReactReconciler");25var ReactUpdates = require("./ReactUpdates");2627var assign = require("./Object.assign");28var emptyObject = require("./emptyObject");29var invariant = require("./invariant");30var shouldUpdateReactComponent = require("./shouldUpdateReactComponent");31var warning = require("./warning");3233function getDeclarationErrorAddendum(component) {34var owner = component._currentElement._owner || null;35if (owner) {36var name = owner.getName();37if (name) {38return ' Check the render method of `' + name + '`.';39}40}41return '';42}4344/**45* ------------------ The Life-Cycle of a Composite Component ------------------46*47* - constructor: Initialization of state. The instance is now retained.48* - componentWillMount49* - render50* - [children's constructors]51* - [children's componentWillMount and render]52* - [children's componentDidMount]53* - componentDidMount54*55* Update Phases:56* - componentWillReceiveProps (only called if parent updated)57* - shouldComponentUpdate58* - componentWillUpdate59* - render60* - [children's constructors or receive props phases]61* - componentDidUpdate62*63* - componentWillUnmount64* - [children's componentWillUnmount]65* - [children destroyed]66* - (destroyed): The instance is now blank, released by React and ready for GC.67*68* -----------------------------------------------------------------------------69*/7071/**72* An incrementing ID assigned to each component when it is mounted. This is73* used to enforce the order in which `ReactUpdates` updates dirty components.74*75* @private76*/77var nextMountID = 1;7879/**80* @lends {ReactCompositeComponent.prototype}81*/82var ReactCompositeComponentMixin = {8384/**85* Base constructor for all composite component.86*87* @param {ReactElement} element88* @final89* @internal90*/91construct: function(element) {92this._currentElement = element;93this._rootNodeID = null;94this._instance = null;9596// See ReactUpdateQueue97this._pendingElement = null;98this._pendingStateQueue = null;99this._pendingReplaceState = false;100this._pendingForceUpdate = false;101102this._renderedComponent = null;103104this._context = null;105this._mountOrder = 0;106this._isTopLevel = false;107108// See ReactUpdates and ReactUpdateQueue.109this._pendingCallbacks = null;110},111112/**113* Initializes the component, renders markup, and registers event listeners.114*115* @param {string} rootID DOM ID of the root node.116* @param {ReactReconcileTransaction|ReactServerRenderingTransaction} transaction117* @return {?string} Rendered markup to be inserted into the DOM.118* @final119* @internal120*/121mountComponent: function(rootID, transaction, context) {122this._context = context;123this._mountOrder = nextMountID++;124this._rootNodeID = rootID;125126var publicProps = this._processProps(this._currentElement.props);127var publicContext = this._processContext(this._currentElement._context);128129var Component = ReactNativeComponent.getComponentClassForElement(130this._currentElement131);132133// Initialize the public class134var inst = new Component(publicProps, publicContext);135136if ("production" !== process.env.NODE_ENV) {137// This will throw later in _renderValidatedComponent, but add an early138// warning now to help debugging139("production" !== process.env.NODE_ENV ? warning(140inst.render != null,141'%s(...): No `render` method found on the returned component ' +142'instance: you may have forgotten to define `render` in your ' +143'component or you may have accidentally tried to render an element ' +144'whose type is a function that isn\'t a React component.',145Component.displayName || Component.name || 'Component'146) : null);147}148149// These should be set up in the constructor, but as a convenience for150// simpler class abstractions, we set them up after the fact.151inst.props = publicProps;152inst.context = publicContext;153inst.refs = emptyObject;154155this._instance = inst;156157// Store a reference from the instance back to the internal representation158ReactInstanceMap.set(inst, this);159160if ("production" !== process.env.NODE_ENV) {161this._warnIfContextsDiffer(this._currentElement._context, context);162}163164if ("production" !== process.env.NODE_ENV) {165// Since plain JS classes are defined without any special initialization166// logic, we can not catch common errors early. Therefore, we have to167// catch them here, at initialization time, instead.168("production" !== process.env.NODE_ENV ? warning(169!inst.getInitialState ||170inst.getInitialState.isReactClassApproved,171'getInitialState was defined on %s, a plain JavaScript class. ' +172'This is only supported for classes created using React.createClass. ' +173'Did you mean to define a state property instead?',174this.getName() || 'a component'175) : null);176("production" !== process.env.NODE_ENV ? warning(177!inst.getDefaultProps ||178inst.getDefaultProps.isReactClassApproved,179'getDefaultProps was defined on %s, a plain JavaScript class. ' +180'This is only supported for classes created using React.createClass. ' +181'Use a static property to define defaultProps instead.',182this.getName() || 'a component'183) : null);184("production" !== process.env.NODE_ENV ? warning(185!inst.propTypes,186'propTypes was defined as an instance property on %s. Use a static ' +187'property to define propTypes instead.',188this.getName() || 'a component'189) : null);190("production" !== process.env.NODE_ENV ? warning(191!inst.contextTypes,192'contextTypes was defined as an instance property on %s. Use a ' +193'static property to define contextTypes instead.',194this.getName() || 'a component'195) : null);196("production" !== process.env.NODE_ENV ? warning(197typeof inst.componentShouldUpdate !== 'function',198'%s has a method called ' +199'componentShouldUpdate(). Did you mean shouldComponentUpdate()? ' +200'The name is phrased as a question because the function is ' +201'expected to return a value.',202(this.getName() || 'A component')203) : null);204}205206var initialState = inst.state;207if (initialState === undefined) {208inst.state = initialState = null;209}210("production" !== process.env.NODE_ENV ? invariant(211typeof initialState === 'object' && !Array.isArray(initialState),212'%s.state: must be set to an object or null',213this.getName() || 'ReactCompositeComponent'214) : invariant(typeof initialState === 'object' && !Array.isArray(initialState)));215216this._pendingStateQueue = null;217this._pendingReplaceState = false;218this._pendingForceUpdate = false;219220var childContext;221var renderedElement;222223var previouslyMounting = ReactLifeCycle.currentlyMountingInstance;224ReactLifeCycle.currentlyMountingInstance = this;225try {226if (inst.componentWillMount) {227inst.componentWillMount();228// When mounting, calls to `setState` by `componentWillMount` will set229// `this._pendingStateQueue` without triggering a re-render.230if (this._pendingStateQueue) {231inst.state = this._processPendingState(inst.props, inst.context);232}233}234235childContext = this._getValidatedChildContext(context);236renderedElement = this._renderValidatedComponent(childContext);237} finally {238ReactLifeCycle.currentlyMountingInstance = previouslyMounting;239}240241this._renderedComponent = this._instantiateReactComponent(242renderedElement,243this._currentElement.type // The wrapping type244);245246var markup = ReactReconciler.mountComponent(247this._renderedComponent,248rootID,249transaction,250this._mergeChildContext(context, childContext)251);252if (inst.componentDidMount) {253transaction.getReactMountReady().enqueue(inst.componentDidMount, inst);254}255256return markup;257},258259/**260* Releases any resources allocated by `mountComponent`.261*262* @final263* @internal264*/265unmountComponent: function() {266var inst = this._instance;267268if (inst.componentWillUnmount) {269var previouslyUnmounting = ReactLifeCycle.currentlyUnmountingInstance;270ReactLifeCycle.currentlyUnmountingInstance = this;271try {272inst.componentWillUnmount();273} finally {274ReactLifeCycle.currentlyUnmountingInstance = previouslyUnmounting;275}276}277278ReactReconciler.unmountComponent(this._renderedComponent);279this._renderedComponent = null;280281// Reset pending fields282this._pendingStateQueue = null;283this._pendingReplaceState = false;284this._pendingForceUpdate = false;285this._pendingCallbacks = null;286this._pendingElement = null;287288// These fields do not really need to be reset since this object is no289// longer accessible.290this._context = null;291this._rootNodeID = null;292293// Delete the reference from the instance to this internal representation294// which allow the internals to be properly cleaned up even if the user295// leaks a reference to the public instance.296ReactInstanceMap.remove(inst);297298// Some existing components rely on inst.props even after they've been299// destroyed (in event handlers).300// TODO: inst.props = null;301// TODO: inst.state = null;302// TODO: inst.context = null;303},304305/**306* Schedule a partial update to the props. Only used for internal testing.307*308* @param {object} partialProps Subset of the next props.309* @param {?function} callback Called after props are updated.310* @final311* @internal312*/313_setPropsInternal: function(partialProps, callback) {314// This is a deoptimized path. We optimize for always having an element.315// This creates an extra internal element.316var element = this._pendingElement || this._currentElement;317this._pendingElement = ReactElement.cloneAndReplaceProps(318element,319assign({}, element.props, partialProps)320);321ReactUpdates.enqueueUpdate(this, callback);322},323324/**325* Filters the context object to only contain keys specified in326* `contextTypes`327*328* @param {object} context329* @return {?object}330* @private331*/332_maskContext: function(context) {333var maskedContext = null;334// This really should be getting the component class for the element,335// but we know that we're not going to need it for built-ins.336if (typeof this._currentElement.type === 'string') {337return emptyObject;338}339var contextTypes = this._currentElement.type.contextTypes;340if (!contextTypes) {341return emptyObject;342}343maskedContext = {};344for (var contextName in contextTypes) {345maskedContext[contextName] = context[contextName];346}347return maskedContext;348},349350/**351* Filters the context object to only contain keys specified in352* `contextTypes`, and asserts that they are valid.353*354* @param {object} context355* @return {?object}356* @private357*/358_processContext: function(context) {359var maskedContext = this._maskContext(context);360if ("production" !== process.env.NODE_ENV) {361var Component = ReactNativeComponent.getComponentClassForElement(362this._currentElement363);364if (Component.contextTypes) {365this._checkPropTypes(366Component.contextTypes,367maskedContext,368ReactPropTypeLocations.context369);370}371}372return maskedContext;373},374375/**376* @param {object} currentContext377* @return {object}378* @private379*/380_getValidatedChildContext: function(currentContext) {381var inst = this._instance;382var childContext = inst.getChildContext && inst.getChildContext();383if (childContext) {384("production" !== process.env.NODE_ENV ? invariant(385typeof inst.constructor.childContextTypes === 'object',386'%s.getChildContext(): childContextTypes must be defined in order to ' +387'use getChildContext().',388this.getName() || 'ReactCompositeComponent'389) : invariant(typeof inst.constructor.childContextTypes === 'object'));390if ("production" !== process.env.NODE_ENV) {391this._checkPropTypes(392inst.constructor.childContextTypes,393childContext,394ReactPropTypeLocations.childContext395);396}397for (var name in childContext) {398("production" !== process.env.NODE_ENV ? invariant(399name in inst.constructor.childContextTypes,400'%s.getChildContext(): key "%s" is not defined in childContextTypes.',401this.getName() || 'ReactCompositeComponent',402name403) : invariant(name in inst.constructor.childContextTypes));404}405return childContext;406}407return null;408},409410_mergeChildContext: function(currentContext, childContext) {411if (childContext) {412return assign({}, currentContext, childContext);413}414return currentContext;415},416417/**418* Processes props by setting default values for unspecified props and419* asserting that the props are valid. Does not mutate its argument; returns420* a new props object with defaults merged in.421*422* @param {object} newProps423* @return {object}424* @private425*/426_processProps: function(newProps) {427if ("production" !== process.env.NODE_ENV) {428var Component = ReactNativeComponent.getComponentClassForElement(429this._currentElement430);431if (Component.propTypes) {432this._checkPropTypes(433Component.propTypes,434newProps,435ReactPropTypeLocations.prop436);437}438}439return newProps;440},441442/**443* Assert that the props are valid444*445* @param {object} propTypes Map of prop name to a ReactPropType446* @param {object} props447* @param {string} location e.g. "prop", "context", "child context"448* @private449*/450_checkPropTypes: function(propTypes, props, location) {451// TODO: Stop validating prop types here and only use the element452// validation.453var componentName = this.getName();454for (var propName in propTypes) {455if (propTypes.hasOwnProperty(propName)) {456var error;457try {458// This is intentionally an invariant that gets caught. It's the same459// behavior as without this statement except with a better message.460("production" !== process.env.NODE_ENV ? invariant(461typeof propTypes[propName] === 'function',462'%s: %s type `%s` is invalid; it must be a function, usually ' +463'from React.PropTypes.',464componentName || 'React class',465ReactPropTypeLocationNames[location],466propName467) : invariant(typeof propTypes[propName] === 'function'));468error = propTypes[propName](props, propName, componentName, location);469} catch (ex) {470error = ex;471}472if (error instanceof Error) {473// We may want to extend this logic for similar errors in474// React.render calls, so I'm abstracting it away into475// a function to minimize refactoring in the future476var addendum = getDeclarationErrorAddendum(this);477478if (location === ReactPropTypeLocations.prop) {479// Preface gives us something to blacklist in warning module480("production" !== process.env.NODE_ENV ? warning(481false,482'Failed Composite propType: %s%s',483error.message,484addendum485) : null);486} else {487("production" !== process.env.NODE_ENV ? warning(488false,489'Failed Context Types: %s%s',490error.message,491addendum492) : null);493}494}495}496}497},498499receiveComponent: function(nextElement, transaction, nextContext) {500var prevElement = this._currentElement;501var prevContext = this._context;502503this._pendingElement = null;504505this.updateComponent(506transaction,507prevElement,508nextElement,509prevContext,510nextContext511);512},513514/**515* If any of `_pendingElement`, `_pendingStateQueue`, or `_pendingForceUpdate`516* is set, update the component.517*518* @param {ReactReconcileTransaction} transaction519* @internal520*/521performUpdateIfNecessary: function(transaction) {522if (this._pendingElement != null) {523ReactReconciler.receiveComponent(524this,525this._pendingElement || this._currentElement,526transaction,527this._context528);529}530531if (this._pendingStateQueue !== null || this._pendingForceUpdate) {532if ("production" !== process.env.NODE_ENV) {533ReactElementValidator.checkAndWarnForMutatedProps(534this._currentElement535);536}537538this.updateComponent(539transaction,540this._currentElement,541this._currentElement,542this._context,543this._context544);545}546},547548/**549* Compare two contexts, warning if they are different550* TODO: Remove this check when owner-context is removed551*/552_warnIfContextsDiffer: function(ownerBasedContext, parentBasedContext) {553ownerBasedContext = this._maskContext(ownerBasedContext);554parentBasedContext = this._maskContext(parentBasedContext);555var parentKeys = Object.keys(parentBasedContext).sort();556var displayName = this.getName() || 'ReactCompositeComponent';557for (var i = 0; i < parentKeys.length; i++) {558var key = parentKeys[i];559("production" !== process.env.NODE_ENV ? warning(560ownerBasedContext[key] === parentBasedContext[key],561'owner-based and parent-based contexts differ ' +562'(values: `%s` vs `%s`) for key (%s) while mounting %s ' +563'(see: http://fb.me/react-context-by-parent)',564ownerBasedContext[key],565parentBasedContext[key],566key,567displayName568) : null);569}570},571572/**573* Perform an update to a mounted component. The componentWillReceiveProps and574* shouldComponentUpdate methods are called, then (assuming the update isn't575* skipped) the remaining update lifecycle methods are called and the DOM576* representation is updated.577*578* By default, this implements React's rendering and reconciliation algorithm.579* Sophisticated clients may wish to override this.580*581* @param {ReactReconcileTransaction} transaction582* @param {ReactElement} prevParentElement583* @param {ReactElement} nextParentElement584* @internal585* @overridable586*/587updateComponent: function(588transaction,589prevParentElement,590nextParentElement,591prevUnmaskedContext,592nextUnmaskedContext593) {594var inst = this._instance;595596var nextContext = inst.context;597var nextProps = inst.props;598599// Distinguish between a props update versus a simple state update600if (prevParentElement !== nextParentElement) {601nextContext = this._processContext(nextParentElement._context);602nextProps = this._processProps(nextParentElement.props);603604if ("production" !== process.env.NODE_ENV) {605if (nextUnmaskedContext != null) {606this._warnIfContextsDiffer(607nextParentElement._context,608nextUnmaskedContext609);610}611}612613// An update here will schedule an update but immediately set614// _pendingStateQueue which will ensure that any state updates gets615// immediately reconciled instead of waiting for the next batch.616617if (inst.componentWillReceiveProps) {618inst.componentWillReceiveProps(nextProps, nextContext);619}620}621622var nextState = this._processPendingState(nextProps, nextContext);623624var shouldUpdate =625this._pendingForceUpdate ||626!inst.shouldComponentUpdate ||627inst.shouldComponentUpdate(nextProps, nextState, nextContext);628629if ("production" !== process.env.NODE_ENV) {630("production" !== process.env.NODE_ENV ? warning(631typeof shouldUpdate !== 'undefined',632'%s.shouldComponentUpdate(): Returned undefined instead of a ' +633'boolean value. Make sure to return true or false.',634this.getName() || 'ReactCompositeComponent'635) : null);636}637638if (shouldUpdate) {639this._pendingForceUpdate = false;640// Will set `this.props`, `this.state` and `this.context`.641this._performComponentUpdate(642nextParentElement,643nextProps,644nextState,645nextContext,646transaction,647nextUnmaskedContext648);649} else {650// If it's determined that a component should not update, we still want651// to set props and state but we shortcut the rest of the update.652this._currentElement = nextParentElement;653this._context = nextUnmaskedContext;654inst.props = nextProps;655inst.state = nextState;656inst.context = nextContext;657}658},659660_processPendingState: function(props, context) {661var inst = this._instance;662var queue = this._pendingStateQueue;663var replace = this._pendingReplaceState;664this._pendingReplaceState = false;665this._pendingStateQueue = null;666667if (!queue) {668return inst.state;669}670671if (replace && queue.length === 1) {672return queue[0];673}674675var nextState = assign({}, replace ? queue[0] : inst.state);676for (var i = replace ? 1 : 0; i < queue.length; i++) {677var partial = queue[i];678assign(679nextState,680typeof partial === 'function' ?681partial.call(inst, nextState, props, context) :682partial683);684}685686return nextState;687},688689/**690* Merges new props and state, notifies delegate methods of update and691* performs update.692*693* @param {ReactElement} nextElement Next element694* @param {object} nextProps Next public object to set as properties.695* @param {?object} nextState Next object to set as state.696* @param {?object} nextContext Next public object to set as context.697* @param {ReactReconcileTransaction} transaction698* @param {?object} unmaskedContext699* @private700*/701_performComponentUpdate: function(702nextElement,703nextProps,704nextState,705nextContext,706transaction,707unmaskedContext708) {709var inst = this._instance;710711var prevProps = inst.props;712var prevState = inst.state;713var prevContext = inst.context;714715if (inst.componentWillUpdate) {716inst.componentWillUpdate(nextProps, nextState, nextContext);717}718719this._currentElement = nextElement;720this._context = unmaskedContext;721inst.props = nextProps;722inst.state = nextState;723inst.context = nextContext;724725this._updateRenderedComponent(transaction, unmaskedContext);726727if (inst.componentDidUpdate) {728transaction.getReactMountReady().enqueue(729inst.componentDidUpdate.bind(inst, prevProps, prevState, prevContext),730inst731);732}733},734735/**736* Call the component's `render` method and update the DOM accordingly.737*738* @param {ReactReconcileTransaction} transaction739* @internal740*/741_updateRenderedComponent: function(transaction, context) {742var prevComponentInstance = this._renderedComponent;743var prevRenderedElement = prevComponentInstance._currentElement;744var childContext = this._getValidatedChildContext();745var nextRenderedElement = this._renderValidatedComponent(childContext);746if (shouldUpdateReactComponent(prevRenderedElement, nextRenderedElement)) {747ReactReconciler.receiveComponent(748prevComponentInstance,749nextRenderedElement,750transaction,751this._mergeChildContext(context, childContext)752);753} else {754// These two IDs are actually the same! But nothing should rely on that.755var thisID = this._rootNodeID;756var prevComponentID = prevComponentInstance._rootNodeID;757ReactReconciler.unmountComponent(prevComponentInstance);758759this._renderedComponent = this._instantiateReactComponent(760nextRenderedElement,761this._currentElement.type762);763var nextMarkup = ReactReconciler.mountComponent(764this._renderedComponent,765thisID,766transaction,767this._mergeChildContext(context, childContext)768);769this._replaceNodeWithMarkupByID(prevComponentID, nextMarkup);770}771},772773/**774* @protected775*/776_replaceNodeWithMarkupByID: function(prevComponentID, nextMarkup) {777ReactComponentEnvironment.replaceNodeWithMarkupByID(778prevComponentID,779nextMarkup780);781},782783/**784* @protected785*/786_renderValidatedComponentWithoutOwnerOrContext: function() {787var inst = this._instance;788var renderedComponent = inst.render();789if ("production" !== process.env.NODE_ENV) {790// We allow auto-mocks to proceed as if they're returning null.791if (typeof renderedComponent === 'undefined' &&792inst.render._isMockFunction) {793// This is probably bad practice. Consider warning here and794// deprecating this convenience.795renderedComponent = null;796}797}798799return renderedComponent;800},801802/**803* @private804*/805_renderValidatedComponent: function(childContext) {806var renderedComponent;807var previousContext = ReactContext.current;808ReactContext.current = this._mergeChildContext(809this._currentElement._context,810childContext811);812ReactCurrentOwner.current = this;813try {814renderedComponent =815this._renderValidatedComponentWithoutOwnerOrContext();816} finally {817ReactContext.current = previousContext;818ReactCurrentOwner.current = null;819}820("production" !== process.env.NODE_ENV ? invariant(821// TODO: An `isValidNode` function would probably be more appropriate822renderedComponent === null || renderedComponent === false ||823ReactElement.isValidElement(renderedComponent),824'%s.render(): A valid ReactComponent must be returned. You may have ' +825'returned undefined, an array or some other invalid object.',826this.getName() || 'ReactCompositeComponent'827) : invariant(// TODO: An `isValidNode` function would probably be more appropriate828renderedComponent === null || renderedComponent === false ||829ReactElement.isValidElement(renderedComponent)));830return renderedComponent;831},832833/**834* Lazily allocates the refs object and stores `component` as `ref`.835*836* @param {string} ref Reference name.837* @param {component} component Component to store as `ref`.838* @final839* @private840*/841attachRef: function(ref, component) {842var inst = this.getPublicInstance();843var refs = inst.refs === emptyObject ? (inst.refs = {}) : inst.refs;844refs[ref] = component.getPublicInstance();845},846847/**848* Detaches a reference name.849*850* @param {string} ref Name to dereference.851* @final852* @private853*/854detachRef: function(ref) {855var refs = this.getPublicInstance().refs;856delete refs[ref];857},858859/**860* Get a text description of the component that can be used to identify it861* in error messages.862* @return {string} The name or null.863* @internal864*/865getName: function() {866var type = this._currentElement.type;867var constructor = this._instance && this._instance.constructor;868return (869type.displayName || (constructor && constructor.displayName) ||870type.name || (constructor && constructor.name) ||871null872);873},874875/**876* Get the publicly accessible representation of this component - i.e. what877* is exposed by refs and returned by React.render. Can be null for stateless878* components.879*880* @return {ReactComponent} the public component instance.881* @internal882*/883getPublicInstance: function() {884return this._instance;885},886887// Stub888_instantiateReactComponent: null889890};891892ReactPerf.measureMethods(893ReactCompositeComponentMixin,894'ReactCompositeComponent',895{896mountComponent: 'mountComponent',897updateComponent: 'updateComponent',898_renderValidatedComponent: '_renderValidatedComponent'899}900);901902var ReactCompositeComponent = {903904Mixin: ReactCompositeComponentMixin905906};907908module.exports = ReactCompositeComponent;909910911