react / react-0.13.3 / examples / basic-commonjs / node_modules / reactify / node_modules / react-tools / src / core / ReactComponent.js
81152 views/**1* Copyright 2013-2014, 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 ReactComponent9*/1011"use strict";1213var ReactElement = require('ReactElement');14var ReactOwner = require('ReactOwner');15var ReactUpdates = require('ReactUpdates');1617var assign = require('Object.assign');18var invariant = require('invariant');19var keyMirror = require('keyMirror');2021/**22* Every React component is in one of these life cycles.23*/24var ComponentLifeCycle = keyMirror({25/**26* Mounted components have a DOM node representation and are capable of27* receiving new props.28*/29MOUNTED: null,30/**31* Unmounted components are inactive and cannot receive new props.32*/33UNMOUNTED: null34});3536var injected = false;3738/**39* Optionally injectable environment dependent cleanup hook. (server vs.40* browser etc). Example: A browser system caches DOM nodes based on component41* ID and must remove that cache entry when this instance is unmounted.42*43* @private44*/45var unmountIDFromEnvironment = null;4647/**48* The "image" of a component tree, is the platform specific (typically49* serialized) data that represents a tree of lower level UI building blocks.50* On the web, this "image" is HTML markup which describes a construction of51* low level `div` and `span` nodes. Other platforms may have different52* encoding of this "image". This must be injected.53*54* @private55*/56var mountImageIntoNode = null;5758/**59* Components are the basic units of composition in React.60*61* Every component accepts a set of keyed input parameters known as "props" that62* are initialized by the constructor. Once a component is mounted, the props63* can be mutated using `setProps` or `replaceProps`.64*65* Every component is capable of the following operations:66*67* `mountComponent`68* Initializes the component, renders markup, and registers event listeners.69*70* `receiveComponent`71* Updates the rendered DOM nodes to match the given component.72*73* `unmountComponent`74* Releases any resources allocated by this component.75*76* Components can also be "owned" by other components. Being owned by another77* component means being constructed by that component. This is different from78* being the child of a component, which means having a DOM representation that79* is a child of the DOM representation of that component.80*81* @class ReactComponent82*/83var ReactComponent = {8485injection: {86injectEnvironment: function(ReactComponentEnvironment) {87invariant(88!injected,89'ReactComponent: injectEnvironment() can only be called once.'90);91mountImageIntoNode = ReactComponentEnvironment.mountImageIntoNode;92unmountIDFromEnvironment =93ReactComponentEnvironment.unmountIDFromEnvironment;94ReactComponent.BackendIDOperations =95ReactComponentEnvironment.BackendIDOperations;96injected = true;97}98},99100/**101* @internal102*/103LifeCycle: ComponentLifeCycle,104105/**106* Injected module that provides ability to mutate individual properties.107* Injected into the base class because many different subclasses need access108* to this.109*110* @internal111*/112BackendIDOperations: null,113114/**115* Base functionality for every ReactComponent constructor. Mixed into the116* `ReactComponent` prototype, but exposed statically for easy access.117*118* @lends {ReactComponent.prototype}119*/120Mixin: {121122/**123* Checks whether or not this component is mounted.124*125* @return {boolean} True if mounted, false otherwise.126* @final127* @protected128*/129isMounted: function() {130return this._lifeCycleState === ComponentLifeCycle.MOUNTED;131},132133/**134* Sets a subset of the props.135*136* @param {object} partialProps Subset of the next props.137* @param {?function} callback Called after props are updated.138* @final139* @public140*/141setProps: function(partialProps, callback) {142// Merge with the pending element if it exists, otherwise with existing143// element props.144var element = this._pendingElement || this._currentElement;145this.replaceProps(146assign({}, element.props, partialProps),147callback148);149},150151/**152* Replaces all of the props.153*154* @param {object} props New props.155* @param {?function} callback Called after props are updated.156* @final157* @public158*/159replaceProps: function(props, callback) {160invariant(161this.isMounted(),162'replaceProps(...): Can only update a mounted component.'163);164invariant(165this._mountDepth === 0,166'replaceProps(...): You called `setProps` or `replaceProps` on a ' +167'component with a parent. This is an anti-pattern since props will ' +168'get reactively updated when rendered. Instead, change the owner\'s ' +169'`render` method to pass the correct value as props to the component ' +170'where it is created.'171);172// This is a deoptimized path. We optimize for always having a element.173// This creates an extra internal element.174this._pendingElement = ReactElement.cloneAndReplaceProps(175this._pendingElement || this._currentElement,176props177);178ReactUpdates.enqueueUpdate(this, callback);179},180181/**182* Schedule a partial update to the props. Only used for internal testing.183*184* @param {object} partialProps Subset of the next props.185* @param {?function} callback Called after props are updated.186* @final187* @internal188*/189_setPropsInternal: function(partialProps, callback) {190// This is a deoptimized path. We optimize for always having a element.191// This creates an extra internal element.192var element = this._pendingElement || this._currentElement;193this._pendingElement = ReactElement.cloneAndReplaceProps(194element,195assign({}, element.props, partialProps)196);197ReactUpdates.enqueueUpdate(this, callback);198},199200/**201* Base constructor for all React components.202*203* Subclasses that override this method should make sure to invoke204* `ReactComponent.Mixin.construct.call(this, ...)`.205*206* @param {ReactElement} element207* @internal208*/209construct: function(element) {210// This is the public exposed props object after it has been processed211// with default props. The element's props represents the true internal212// state of the props.213this.props = element.props;214// Record the component responsible for creating this component.215// This is accessible through the element but we maintain an extra216// field for compatibility with devtools and as a way to make an217// incremental update. TODO: Consider deprecating this field.218this._owner = element._owner;219220// All components start unmounted.221this._lifeCycleState = ComponentLifeCycle.UNMOUNTED;222223// See ReactUpdates.224this._pendingCallbacks = null;225226// We keep the old element and a reference to the pending element227// to track updates.228this._currentElement = element;229this._pendingElement = null;230},231232/**233* Initializes the component, renders markup, and registers event listeners.234*235* NOTE: This does not insert any nodes into the DOM.236*237* Subclasses that override this method should make sure to invoke238* `ReactComponent.Mixin.mountComponent.call(this, ...)`.239*240* @param {string} rootID DOM ID of the root node.241* @param {ReactReconcileTransaction|ReactServerRenderingTransaction} transaction242* @param {number} mountDepth number of components in the owner hierarchy.243* @return {?string} Rendered markup to be inserted into the DOM.244* @internal245*/246mountComponent: function(rootID, transaction, mountDepth) {247invariant(248!this.isMounted(),249'mountComponent(%s, ...): Can only mount an unmounted component. ' +250'Make sure to avoid storing components between renders or reusing a ' +251'single component instance in multiple places.',252rootID253);254var ref = this._currentElement.ref;255if (ref != null) {256var owner = this._currentElement._owner;257ReactOwner.addComponentAsRefTo(this, ref, owner);258}259this._rootNodeID = rootID;260this._lifeCycleState = ComponentLifeCycle.MOUNTED;261this._mountDepth = mountDepth;262// Effectively: return '';263},264265/**266* Releases any resources allocated by `mountComponent`.267*268* NOTE: This does not remove any nodes from the DOM.269*270* Subclasses that override this method should make sure to invoke271* `ReactComponent.Mixin.unmountComponent.call(this)`.272*273* @internal274*/275unmountComponent: function() {276invariant(277this.isMounted(),278'unmountComponent(): Can only unmount a mounted component.'279);280var ref = this._currentElement.ref;281if (ref != null) {282ReactOwner.removeComponentAsRefFrom(this, ref, this._owner);283}284unmountIDFromEnvironment(this._rootNodeID);285this._rootNodeID = null;286this._lifeCycleState = ComponentLifeCycle.UNMOUNTED;287},288289/**290* Given a new instance of this component, updates the rendered DOM nodes291* as if that instance was rendered instead.292*293* Subclasses that override this method should make sure to invoke294* `ReactComponent.Mixin.receiveComponent.call(this, ...)`.295*296* @param {object} nextComponent Next set of properties.297* @param {ReactReconcileTransaction} transaction298* @internal299*/300receiveComponent: function(nextElement, transaction) {301invariant(302this.isMounted(),303'receiveComponent(...): Can only update a mounted component.'304);305this._pendingElement = nextElement;306this.performUpdateIfNecessary(transaction);307},308309/**310* If `_pendingElement` is set, update the component.311*312* @param {ReactReconcileTransaction} transaction313* @internal314*/315performUpdateIfNecessary: function(transaction) {316if (this._pendingElement == null) {317return;318}319var prevElement = this._currentElement;320var nextElement = this._pendingElement;321this._currentElement = nextElement;322this.props = nextElement.props;323this._owner = nextElement._owner;324this._pendingElement = null;325this.updateComponent(transaction, prevElement);326},327328/**329* Updates the component's currently mounted representation.330*331* @param {ReactReconcileTransaction} transaction332* @param {object} prevElement333* @internal334*/335updateComponent: function(transaction, prevElement) {336var nextElement = this._currentElement;337338// If either the owner or a `ref` has changed, make sure the newest owner339// has stored a reference to `this`, and the previous owner (if different)340// has forgotten the reference to `this`. We use the element instead341// of the public this.props because the post processing cannot determine342// a ref. The ref conceptually lives on the element.343344// TODO: Should this even be possible? The owner cannot change because345// it's forbidden by shouldUpdateReactComponent. The ref can change346// if you swap the keys of but not the refs. Reconsider where this check347// is made. It probably belongs where the key checking and348// instantiateReactComponent is done.349350if (nextElement._owner !== prevElement._owner ||351nextElement.ref !== prevElement.ref) {352if (prevElement.ref != null) {353ReactOwner.removeComponentAsRefFrom(354this, prevElement.ref, prevElement._owner355);356}357// Correct, even if the owner is the same, and only the ref has changed.358if (nextElement.ref != null) {359ReactOwner.addComponentAsRefTo(360this,361nextElement.ref,362nextElement._owner363);364}365}366},367368/**369* Mounts this component and inserts it into the DOM.370*371* @param {string} rootID DOM ID of the root node.372* @param {DOMElement} container DOM element to mount into.373* @param {boolean} shouldReuseMarkup If true, do not insert markup374* @final375* @internal376* @see {ReactMount.render}377*/378mountComponentIntoNode: function(rootID, container, shouldReuseMarkup) {379var transaction = ReactUpdates.ReactReconcileTransaction.getPooled();380transaction.perform(381this._mountComponentIntoNode,382this,383rootID,384container,385transaction,386shouldReuseMarkup387);388ReactUpdates.ReactReconcileTransaction.release(transaction);389},390391/**392* @param {string} rootID DOM ID of the root node.393* @param {DOMElement} container DOM element to mount into.394* @param {ReactReconcileTransaction} transaction395* @param {boolean} shouldReuseMarkup If true, do not insert markup396* @final397* @private398*/399_mountComponentIntoNode: function(400rootID,401container,402transaction,403shouldReuseMarkup) {404var markup = this.mountComponent(rootID, transaction, 0);405mountImageIntoNode(markup, container, shouldReuseMarkup);406},407408/**409* Checks if this component is owned by the supplied `owner` component.410*411* @param {ReactComponent} owner Component to check.412* @return {boolean} True if `owners` owns this component.413* @final414* @internal415*/416isOwnedBy: function(owner) {417return this._owner === owner;418},419420/**421* Gets another component, that shares the same owner as this one, by ref.422*423* @param {string} ref of a sibling Component.424* @return {?ReactComponent} the actual sibling Component.425* @final426* @internal427*/428getSiblingByRef: function(ref) {429var owner = this._owner;430if (!owner || !owner.refs) {431return null;432}433return owner.refs[ref];434}435}436};437438module.exports = ReactComponent;439440441