diff --git a/src/browser/ui/ReactComponentBrowserEnvironment.js b/src/browser/ui/ReactComponentBrowserEnvironment.js index ed8df43ae6..e5b3bbd116 100644 --- a/src/browser/ui/ReactComponentBrowserEnvironment.js +++ b/src/browser/ui/ReactComponentBrowserEnvironment.js @@ -15,16 +15,19 @@ var ReactDOMIDOperations = require('ReactDOMIDOperations'); var ReactMount = require('ReactMount'); -var ReactReconcileTransaction = require('ReactReconcileTransaction'); /** - * Abstracts away all functionality of `ReactComponent` requires knowledge of - * the browser context. + * Abstracts away all functionality of the reconciler that requires knowledge of + * the browser context. TODO: These callers should be refactored to avoid the + * need for this injection. */ var ReactComponentBrowserEnvironment = { - ReactReconcileTransaction: ReactReconcileTransaction, - BackendIDOperations: ReactDOMIDOperations, + processChildrenUpdates: + ReactDOMIDOperations.dangerouslyProcessChildrenUpdates, + + replaceNodeWithMarkupByID: + ReactDOMIDOperations.dangerouslyReplaceNodeWithMarkupByID, /** * If a particular environment requires that some resources be cleaned up, diff --git a/src/browser/ui/ReactDOMComponent.js b/src/browser/ui/ReactDOMComponent.js index 88fbbee471..b9cf620548 100644 --- a/src/browser/ui/ReactDOMComponent.js +++ b/src/browser/ui/ReactDOMComponent.js @@ -39,6 +39,11 @@ var STYLE = keyOf({style: null}); var ELEMENT_NODE_TYPE = 1; +/** + * Optionally injectable operations for mutating the DOM + */ +var BackendIDOperations = null; + /** * @param {?object} props */ @@ -149,6 +154,7 @@ function ReactDOMComponent(tag) { this._tag = tag; this._renderedChildren = null; this._previousStyleCopy = null; + this._rootNodeID = null; } ReactDOMComponent.displayName = 'ReactDOMComponent'; @@ -177,7 +183,7 @@ ReactDOMComponent.Mixin = { mountDepth, context ); - this._previousStyleCopy = null; + this._rootNodeID = rootID; assertValidProps(this._currentElement.props); var closeTag = omittedCloseTags[this._tag] ? '' : ''; return ( @@ -370,7 +376,7 @@ ReactDOMComponent.Mixin = { } else if ( DOMProperty.isStandardName[propKey] || DOMProperty.isCustomAttribute(propKey)) { - ReactComponent.BackendIDOperations.deletePropertyByID( + BackendIDOperations.deletePropertyByID( this._rootNodeID, propKey ); @@ -414,7 +420,7 @@ ReactDOMComponent.Mixin = { } else if ( DOMProperty.isStandardName[propKey] || DOMProperty.isCustomAttribute(propKey)) { - ReactComponent.BackendIDOperations.updatePropertyByID( + BackendIDOperations.updatePropertyByID( this._rootNodeID, propKey, nextProp @@ -422,7 +428,7 @@ ReactDOMComponent.Mixin = { } } if (styleUpdates) { - ReactComponent.BackendIDOperations.updateStylesByID( + BackendIDOperations.updateStylesByID( this._rootNodeID, styleUpdates ); @@ -472,7 +478,7 @@ ReactDOMComponent.Mixin = { } } else if (nextHtml != null) { if (lastHtml !== nextHtml) { - ReactComponent.BackendIDOperations.updateInnerHTMLByID( + BackendIDOperations.updateInnerHTMLByID( this._rootNodeID, nextHtml ); @@ -492,6 +498,8 @@ ReactDOMComponent.Mixin = { this.unmountChildren(); ReactBrowserEventEmitter.deleteAllListeners(this._rootNodeID); ReactComponent.Mixin.unmountComponent.call(this); + ReactMount.purgeID(this._rootNodeID); + this._rootNodeID = null; } }; @@ -503,4 +511,10 @@ assign( ReactMultiChild.Mixin ); +ReactDOMComponent.injection = { + injectIDOperations: function(IDOperations) { + ReactDOMComponent.BackendIDOperations = BackendIDOperations = IDOperations; + } +}; + module.exports = ReactDOMComponent; diff --git a/src/browser/ui/ReactDOMIDOperations.js b/src/browser/ui/ReactDOMIDOperations.js index 469d842b39..b5c37d47fc 100644 --- a/src/browser/ui/ReactDOMIDOperations.js +++ b/src/browser/ui/ReactDOMIDOperations.js @@ -37,7 +37,7 @@ var INVALID_PROPERTY_ERRORS = { /** * Operations used to process updates to DOM nodes. This is made injectable via - * `ReactComponent.BackendIDOperations`. + * `ReactDOMComponent.BackendIDOperations`. */ var ReactDOMIDOperations = { diff --git a/src/browser/ui/ReactDOMTextComponent.js b/src/browser/ui/ReactDOMTextComponent.js index bd2698bf6a..54e5c7acf2 100644 --- a/src/browser/ui/ReactDOMTextComponent.js +++ b/src/browser/ui/ReactDOMTextComponent.js @@ -13,9 +13,9 @@ "use strict"; var DOMPropertyOperations = require('DOMPropertyOperations'); -var ReactComponent = require('ReactComponent'); var ReactComponentBrowserEnvironment = require('ReactComponentBrowserEnvironment'); +var ReactDOMComponent = require('ReactDOMComponent'); var assign = require('Object.assign'); var escapeTextForBrowser = require('escapeTextForBrowser'); @@ -100,7 +100,7 @@ assign(ReactDOMTextComponent.prototype, { // and/or updateComponent to do the actual update for consistency with // other component types? this._stringText = nextStringText; - ReactComponent.BackendIDOperations.updateTextContentByID( + ReactDOMComponent.BackendIDOperations.updateTextContentByID( this._rootNodeID, nextStringText ); diff --git a/src/browser/ui/ReactDefaultInjection.js b/src/browser/ui/ReactDefaultInjection.js index 298917face..1156f338a0 100644 --- a/src/browser/ui/ReactDefaultInjection.js +++ b/src/browser/ui/ReactDefaultInjection.js @@ -28,6 +28,7 @@ var ReactDOMComponent = require('ReactDOMComponent'); var ReactDOMButton = require('ReactDOMButton'); var ReactDOMForm = require('ReactDOMForm'); var ReactDOMImg = require('ReactDOMImg'); +var ReactDOMIDOperations = require('ReactDOMIDOperations'); var ReactDOMInput = require('ReactDOMInput'); var ReactDOMOption = require('ReactDOMOption'); var ReactDOMSelect = require('ReactDOMSelect'); @@ -37,6 +38,7 @@ var ReactEventListener = require('ReactEventListener'); var ReactInjection = require('ReactInjection'); var ReactInstanceHandles = require('ReactInstanceHandles'); var ReactMount = require('ReactMount'); +var ReactReconcileTransaction = require('ReactReconcileTransaction'); var SelectEventPlugin = require('SelectEventPlugin'); var ServerReactRootIndex = require('ServerReactRootIndex'); var SimpleEventPlugin = require('SimpleEventPlugin'); @@ -102,7 +104,7 @@ function inject() { ReactInjection.EmptyComponent.injectEmptyComponent('noscript'); ReactInjection.Updates.injectReconcileTransaction( - ReactComponentBrowserEnvironment.ReactReconcileTransaction + ReactReconcileTransaction ); ReactInjection.Updates.injectBatchingStrategy( ReactDefaultBatchingStrategy @@ -115,6 +117,7 @@ function inject() { ); ReactInjection.Component.injectEnvironment(ReactComponentBrowserEnvironment); + ReactInjection.DOMComponent.injectIDOperations(ReactDOMIDOperations); if (__DEV__) { var url = (ExecutionEnvironment.canUseDOM && window.location.href) || ''; diff --git a/src/browser/ui/ReactInjection.js b/src/browser/ui/ReactInjection.js index 8800c7de4d..bea0124f4a 100644 --- a/src/browser/ui/ReactInjection.js +++ b/src/browser/ui/ReactInjection.js @@ -13,18 +13,20 @@ var DOMProperty = require('DOMProperty'); var EventPluginHub = require('EventPluginHub'); -var ReactComponent = require('ReactComponent'); +var ReactComponentEnvironment = require('ReactComponentEnvironment'); var ReactClass = require('ReactClass'); var ReactEmptyComponent = require('ReactEmptyComponent'); var ReactBrowserEventEmitter = require('ReactBrowserEventEmitter'); var ReactNativeComponent = require('ReactNativeComponent'); +var ReactDOMComponent = require('ReactDOMComponent'); var ReactPerf = require('ReactPerf'); var ReactRootIndex = require('ReactRootIndex'); var ReactUpdates = require('ReactUpdates'); var ReactInjection = { - Component: ReactComponent.injection, + Component: ReactComponentEnvironment.injection, Class: ReactClass.injection, + DOMComponent: ReactDOMComponent.injection, DOMProperty: DOMProperty.injection, EmptyComponent: ReactEmptyComponent.injection, EventPluginHub: EventPluginHub.injection, diff --git a/src/core/ReactComponent.js b/src/core/ReactComponent.js index 1f27a07104..e51b09e94c 100644 --- a/src/core/ReactComponent.js +++ b/src/core/ReactComponent.js @@ -16,17 +16,6 @@ var ReactRef = require('ReactRef'); var invariant = require('invariant'); -var injected = false; - -/** - * Optionally injectable environment dependent cleanup hook. (server vs. - * browser etc). Example: A browser system caches DOM nodes based on component - * ID and must remove that cache entry when this instance is unmounted. - * - * @private - */ -var unmountIDFromEnvironment = null; - function attachRef(ref, component, owner) { if (ref instanceof ReactRef) { ReactRef.attachRef(ref, component); @@ -70,20 +59,6 @@ function detachRef(ref, component, owner) { */ var ReactComponent = { - injection: { - injectEnvironment: function(ReactComponentEnvironment) { - invariant( - !injected, - 'ReactComponent: injectEnvironment() can only be called once.' - ); - unmountIDFromEnvironment = - ReactComponentEnvironment.unmountIDFromEnvironment; - ReactComponent.BackendIDOperations = - ReactComponentEnvironment.BackendIDOperations; - injected = true; - } - }, - /** * Injected module that provides ability to mutate individual properties. * Injected into the base class because many different subclasses need access @@ -114,7 +89,6 @@ var ReactComponent = { // We keep the old element and a reference to the pending element // to track updates. this._currentElement = element; - this._rootNodeID = null; this._mountIndex = 0; this._mountDepth = 0; }, @@ -140,7 +114,6 @@ var ReactComponent = { var owner = this._currentElement._owner; attachRef(ref, this, owner); } - this._rootNodeID = rootID; this._mountDepth = mountDepth; // Effectively: return ''; }, @@ -160,9 +133,6 @@ var ReactComponent = { if (ref != null) { detachRef(ref, this, this._currentElement._owner); } - unmountIDFromEnvironment(this._rootNodeID); - // Reset all fields - this._rootNodeID = null; }, /** diff --git a/src/core/ReactComponentEnvironment.js b/src/core/ReactComponentEnvironment.js new file mode 100644 index 0000000000..48f3b50d06 --- /dev/null +++ b/src/core/ReactComponentEnvironment.js @@ -0,0 +1,57 @@ +/** + * Copyright 2014, 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 ReactComponentEnvironment + */ + +"use strict"; + +var invariant = require('invariant'); + +var injected = false; + +var ReactComponentEnvironment = { + + /** + * Optionally injectable environment dependent cleanup hook. (server vs. + * browser etc). Example: A browser system caches DOM nodes based on component + * ID and must remove that cache entry when this instance is unmounted. + */ + unmountIDFromEnvironment: null, + + /** + * Optionally injectable hook for swapping out mount images in the middle of + * the tree. + */ + replaceNodeWithMarkupByID: null, + + /** + * Optionally injectable hook for processing a queue of child updates. Will + * later move into MultiChildComponents. + */ + processChildrenUpdates: null, + + injection: { + injectEnvironment: function(environment) { + invariant( + !injected, + 'ReactCompositeComponent: injectEnvironment() can only be called once.' + ); + ReactComponentEnvironment.unmountIDFromEnvironment = + environment.unmountIDFromEnvironment; + ReactComponentEnvironment.replaceNodeWithMarkupByID = + environment.replaceNodeWithMarkupByID; + ReactComponentEnvironment.processChildrenUpdates = + environment.processChildrenUpdates; + injected = true; + } + }, + +}; + +module.exports = ReactComponentEnvironment; diff --git a/src/core/ReactCompositeComponent.js b/src/core/ReactCompositeComponent.js index d636ed5505..bcfb322454 100644 --- a/src/core/ReactCompositeComponent.js +++ b/src/core/ReactCompositeComponent.js @@ -12,6 +12,7 @@ "use strict"; var ReactComponent = require('ReactComponent'); +var ReactComponentEnvironment = require('ReactComponentEnvironment'); var ReactContext = require('ReactContext'); var ReactCurrentOwner = require('ReactCurrentOwner'); var ReactElement = require('ReactElement'); @@ -112,6 +113,8 @@ var ReactCompositeComponentMixin = assign({}, * @internal */ construct: function(element) { + this._rootNodeID = null; + this._instance.props = element.props; this._instance.state = null; this._instance.context = null; @@ -168,6 +171,7 @@ var ReactCompositeComponentMixin = assign({}, ); this._context = context; + this._rootNodeID = rootID; var inst = this._instance; @@ -259,7 +263,10 @@ var ReactCompositeComponentMixin = assign({}, ReactComponent.Mixin.unmountComponent.call(this); + ReactComponentEnvironment.unmountIDFromEnvironment(this._rootNodeID); + this._context = null; + this._rootNodeID = null; // Delete the reference from the instance to this internal representation // which allow the internals to be properly cleaned up even if the user @@ -780,7 +787,7 @@ var ReactCompositeComponentMixin = assign({}, this._mountDepth + 1, context ); - ReactComponent.BackendIDOperations.dangerouslyReplaceNodeWithMarkupByID( + ReactComponentEnvironment.replaceNodeWithMarkupByID( prevComponentID, nextMarkup ); diff --git a/src/core/ReactMultiChild.js b/src/core/ReactMultiChild.js index 11da634fc0..b2f695a6e9 100644 --- a/src/core/ReactMultiChild.js +++ b/src/core/ReactMultiChild.js @@ -12,7 +12,7 @@ "use strict"; -var ReactComponent = require('ReactComponent'); +var ReactComponentEnvironment = require('ReactComponentEnvironment'); var ReactMultiChildUpdateTypes = require('ReactMultiChildUpdateTypes'); var flattenChildren = require('flattenChildren'); @@ -136,7 +136,7 @@ function enqueueTextContent(parentID, textContent) { */ function processQueue() { if (updateQueue.length) { - ReactComponent.BackendIDOperations.dangerouslyProcessChildrenUpdates( + ReactComponentEnvironment.processChildrenUpdates( updateQueue, markupQueue );