From 690409a91239e04a38f3f819f8c5b13a6c60b114 Mon Sep 17 00:00:00 2001 From: Sebastian Markbage Date: Fri, 23 Jan 2015 10:04:25 -0800 Subject: [PATCH] Replace ReactComponentMixin with ReactReconciler Instead of putting the shared code in a base class method, we use a wrapper call around all invokations. That way they're free to add code before AND after the non-shared code. That way we ensure that component extensions don't need to implement ReactComponentMixin and do super() calls into it. This helps to create a tighter API for custom component extensions. This provides the first step towards moving these methods to static methods which allows to use a different dispatch mechanism instead of virtual method calls. E.g. pattern matching. --- src/browser/ui/React.js | 3 +- src/browser/ui/ReactDOMComponent.js | 33 +--- src/browser/ui/ReactDOMTextComponent.js | 7 - src/browser/ui/ReactMount.js | 7 +- .../ui/__tests__/ReactDOMComponent-test.js | 4 +- src/core/ReactChildReconciler.js | 12 +- src/core/ReactComponent.js | 147 ---------------- src/core/ReactCompositeComponent.js | 157 ++++++------------ src/core/ReactMultiChild.js | 9 +- src/core/ReactReconciler.js | 107 ++++++++++++ .../__tests__/ReactComponentLifeCycle-test.js | 2 - .../__tests__/ReactCompositeComponent-test.js | 2 - .../__tests__/ReactMockedComponent-test.js | 2 - src/core/instantiateReactComponent.js | 6 + 14 files changed, 192 insertions(+), 306 deletions(-) delete mode 100644 src/core/ReactComponent.js create mode 100644 src/core/ReactReconciler.js diff --git a/src/browser/ui/React.js b/src/browser/ui/React.js index 276bce3d8a..3e8a5159b9 100644 --- a/src/browser/ui/React.js +++ b/src/browser/ui/React.js @@ -16,7 +16,6 @@ var DOMPropertyOperations = require('DOMPropertyOperations'); var EventPluginUtils = require('EventPluginUtils'); var ReactChildren = require('ReactChildren'); -var ReactComponent = require('ReactComponent'); var ReactComponentBase = require('ReactComponentBase'); var ReactClass = require('ReactClass'); var ReactContext = require('ReactContext'); @@ -86,7 +85,7 @@ if ( typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ !== 'undefined' && typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.inject === 'function') { __REACT_DEVTOOLS_GLOBAL_HOOK__.inject({ - Component: ReactComponent, + // TODO: Inject a hook for notifying devtools of updates CurrentOwner: ReactCurrentOwner, DOMComponent: ReactDOMComponent, DOMPropertyOperations: DOMPropertyOperations, diff --git a/src/browser/ui/ReactDOMComponent.js b/src/browser/ui/ReactDOMComponent.js index 59d57ca259..e27634234b 100644 --- a/src/browser/ui/ReactDOMComponent.js +++ b/src/browser/ui/ReactDOMComponent.js @@ -17,7 +17,6 @@ var CSSPropertyOperations = require('CSSPropertyOperations'); var DOMProperty = require('DOMProperty'); var DOMPropertyOperations = require('DOMPropertyOperations'); -var ReactComponent = require('ReactComponent'); var ReactBrowserEventEmitter = require('ReactBrowserEventEmitter'); var ReactMount = require('ReactMount'); var ReactMultiChild = require('ReactMultiChild'); @@ -162,7 +161,6 @@ function validateDangerousTag(tag) { * object mapping of style properties to values. * * @constructor ReactDOMComponent - * @extends ReactComponent * @extends ReactMultiChild */ function ReactDOMComponent(tag) { @@ -177,6 +175,10 @@ ReactDOMComponent.displayName = 'ReactDOMComponent'; ReactDOMComponent.Mixin = { + construct: function(element) { + this._currentElement = element; + }, + /** * Generates root tag markup then recurses. This method has side effects and * is not idempotent. @@ -187,12 +189,6 @@ ReactDOMComponent.Mixin = { * @return {string} The computed markup. */ mountComponent: function(rootID, transaction, context) { - ReactComponent.Mixin.mountComponent.call( - this, - rootID, - transaction, - context - ); this._rootNodeID = rootID; assertValidProps(this._currentElement.props); var closeTag = omittedCloseTags[this._tag] ? '' : ''; @@ -300,18 +296,6 @@ ReactDOMComponent.Mixin = { }, receiveComponent: function(nextElement, transaction, context) { - if (nextElement === this._currentElement && - nextElement._owner != null) { - // Since elements are immutable after the owner is rendered, - // we can do a cheap identity compare here to determine if this is a - // superfluous reconcile. It's possible for state to be mutable but such - // change should trigger an update of the owner which would recreate - // the element. We explicitly check for the existence of an owner since - // it's possible for an element created outside a composite to be - // deeply mutated and reused. - return; - } - var prevElement = this._currentElement; this._currentElement = nextElement; this.updateComponent(transaction, prevElement, nextElement, context); @@ -329,13 +313,6 @@ ReactDOMComponent.Mixin = { */ updateComponent: function(transaction, prevElement, nextElement, context) { assertValidProps(this._currentElement.props); - ReactComponent.Mixin.updateComponent.call( - this, - transaction, - prevElement, - nextElement, - context - ); this._updateDOMProperties(prevElement.props, transaction); this._updateDOMChildren(prevElement.props, transaction, context); }, @@ -498,7 +475,6 @@ ReactDOMComponent.Mixin = { unmountComponent: function() { this.unmountChildren(); ReactBrowserEventEmitter.deleteAllListeners(this._rootNodeID); - ReactComponent.Mixin.unmountComponent.call(this); ReactMount.purgeID(this._rootNodeID); this._rootNodeID = null; } @@ -512,7 +488,6 @@ ReactPerf.measureMethods(ReactDOMComponent, 'ReactDOMComponent', { assign( ReactDOMComponent.prototype, - ReactComponent.Mixin, ReactDOMComponent.Mixin, ReactMultiChild.Mixin ); diff --git a/src/browser/ui/ReactDOMTextComponent.js b/src/browser/ui/ReactDOMTextComponent.js index 3e15a7863b..fe1d1f6d5c 100644 --- a/src/browser/ui/ReactDOMTextComponent.js +++ b/src/browser/ui/ReactDOMTextComponent.js @@ -107,13 +107,6 @@ assign(ReactDOMTextComponent.prototype, { } }, - updateComponent: function() { - invariant( - false, - 'ReactDOMTextComponent: updateComponent() should never be called' - ); - }, - unmountComponent: function() { // TODO: Is this necessary? ReactComponentBrowserEnvironment.unmountIDFromEnvironment(this._rootNodeID); diff --git a/src/browser/ui/ReactMount.js b/src/browser/ui/ReactMount.js index 5bb44032be..a71b2311d4 100644 --- a/src/browser/ui/ReactMount.js +++ b/src/browser/ui/ReactMount.js @@ -21,6 +21,7 @@ var ReactInstanceHandles = require('ReactInstanceHandles'); var ReactInstanceMap = require('ReactInstanceMap'); var ReactMarkupChecksum = require('ReactMarkupChecksum'); var ReactPerf = require('ReactPerf'); +var ReactReconciler = require('ReactReconciler'); var ReactUpdates = require('ReactUpdates'); var emptyObject = require('emptyObject'); @@ -241,7 +242,9 @@ function mountComponentIntoNode( container, transaction, shouldReuseMarkup) { - var markup = this.mountComponent(rootID, transaction, emptyObject); + var markup = ReactReconciler.mountComponent( + this, rootID, transaction, emptyObject + ); this._isTopLevel = true; ReactMount._mountImageIntoNode(markup, container, shouldReuseMarkup); } @@ -553,7 +556,7 @@ var ReactMount = { * @see {ReactMount.unmountComponentAtNode} */ unmountComponentFromNode: function(instance, container) { - instance.unmountComponent(); + ReactReconciler.unmountComponent(instance); if (container.nodeType === DOC_NODE_TYPE) { container = container.documentElement; diff --git a/src/browser/ui/__tests__/ReactDOMComponent-test.js b/src/browser/ui/__tests__/ReactDOMComponent-test.js index 830ee71628..8c28842445 100644 --- a/src/browser/ui/__tests__/ReactDOMComponent-test.js +++ b/src/browser/ui/__tests__/ReactDOMComponent-test.js @@ -308,15 +308,13 @@ describe('ReactDOMComponent', function() { beforeEach(function() { require('mock-modules').dumpCache(); - var ReactComponent = require('ReactComponent'); var ReactMultiChild = require('ReactMultiChild'); var ReactDOMComponent = require('ReactDOMComponent'); var ReactReconcileTransaction = require('ReactReconcileTransaction'); var StubNativeComponent = function(element) { - ReactComponent.Mixin.construct.call(this, element); + this._currentElement = element; }; - assign(StubNativeComponent.prototype, ReactComponent.Mixin); assign(StubNativeComponent.prototype, ReactDOMComponent.Mixin); assign(StubNativeComponent.prototype, ReactMultiChild.Mixin); diff --git a/src/core/ReactChildReconciler.js b/src/core/ReactChildReconciler.js index 47b11dca5f..8507ab7e5f 100644 --- a/src/core/ReactChildReconciler.js +++ b/src/core/ReactChildReconciler.js @@ -12,6 +12,8 @@ 'use strict'; +var ReactReconciler = require('ReactReconciler'); + var flattenChildren = require('flattenChildren'); var instantiateReactComponent = require('instantiateReactComponent'); var shouldUpdateReactComponent = require('shouldUpdateReactComponent'); @@ -78,11 +80,13 @@ var ReactChildReconciler = { var prevElement = prevChild && prevChild._currentElement; var nextElement = nextChildren[name]; if (shouldUpdateReactComponent(prevElement, nextElement)) { - prevChild.receiveComponent(nextElement, transaction, context); + ReactReconciler.receiveComponent( + prevChild, nextElement, transaction, context + ); nextChildren[name] = prevChild; } else { if (prevChild) { - prevChild.unmountComponent(); + ReactReconciler.unmountComponent(prevChild, name); } // The child must be instantiated before it's mounted. var nextChildInstance = instantiateReactComponent( @@ -96,7 +100,7 @@ var ReactChildReconciler = { for (name in prevChildren) { if (prevChildren.hasOwnProperty(name) && !(nextChildren && nextChildren.hasOwnProperty(name))) { - prevChildren[name].unmountComponent(); + ReactReconciler.unmountComponent(prevChildren[name]); } } return nextChildren; @@ -112,7 +116,7 @@ var ReactChildReconciler = { unmountChildren: function(renderedChildren) { for (var name in renderedChildren) { var renderedChild = renderedChildren[name]; - renderedChild.unmountComponent(); + ReactReconciler.unmountComponent(renderedChild); } } diff --git a/src/core/ReactComponent.js b/src/core/ReactComponent.js deleted file mode 100644 index a9a8753893..0000000000 --- a/src/core/ReactComponent.js +++ /dev/null @@ -1,147 +0,0 @@ -/** - * Copyright 2013-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 ReactComponent - */ - -'use strict'; - -var ReactElementValidator = require('ReactElementValidator'); - -var invariant = require('invariant'); - -/** - * Components are the basic units of composition in React. - * - * Every component accepts a set of keyed input parameters known as "props" that - * are initialized by the constructor. Once a component is mounted, the props - * can be mutated using `setProps` or `replaceProps`. - * - * Every component is capable of the following operations: - * - * `mountComponent` - * Initializes the component, renders markup, and registers event listeners. - * - * `receiveComponent` - * Updates the rendered DOM nodes to match the given component. - * - * `unmountComponent` - * Releases any resources allocated by this component. - * - * Components can also be "owned" by other components. Being owned by another - * component means being constructed by that component. This is different from - * being the child of a component, which means having a DOM representation that - * is a child of the DOM representation of that component. - * - * @class ReactComponent - */ -var ReactComponent = { - - /** - * Injected module that provides ability to mutate individual properties. - * Injected into the base class because many different subclasses need access - * to this. - * - * @internal - */ - BackendIDOperations: null, - - /** - * Base functionality for every ReactComponent constructor. Mixed into the - * `ReactComponent` prototype, but exposed statically for easy access. - * - * @lends {ReactComponent.prototype} - */ - Mixin: { - - /** - * Base constructor for all React components. - * - * Subclasses that override this method should make sure to invoke - * `ReactComponent.Mixin.construct.call(this, ...)`. - * - * @param {ReactElement} element - * @internal - */ - construct: function(element) { - // We keep the old element and a reference to the pending element - // to track updates. - this._currentElement = element; - // These two fields are used by the DOM and ART diffing algorithms - // respectively. Instead of using expandos on components, we should be - // storing the state needed by the diffing algorithms elsewhere. - this._mountIndex = 0; - this._mountImage = null; - }, - - /** - * Initializes the component, renders markup, and registers event listeners. - * - * NOTE: This does not insert any nodes into the DOM. - * - * Subclasses that override this method should make sure to invoke - * `ReactComponent.Mixin.mountComponent.call(this, ...)`. - * - * @param {string} rootID DOM ID of the root node. - * @param {ReactReconcileTransaction|ReactServerRenderingTransaction} transaction - * @return {?string} Rendered markup to be inserted into the DOM. - * @internal - */ - mountComponent: function(rootID, transaction, context) { - if (__DEV__) { - ReactElementValidator.checkAndWarnForMutatedProps(this._currentElement); - } - // Effectively: return ''; - }, - - /** - * Releases any resources allocated by `mountComponent`. - * - * NOTE: This does not remove any nodes from the DOM. - * - * Subclasses that override this method should make sure to invoke - * `ReactComponent.Mixin.unmountComponent.call(this)`. - * - * @internal - */ - unmountComponent: function() { - }, - - /** - * Updates the component's currently mounted representation. - * - * @param {ReactReconcileTransaction} transaction - * @param {object} prevElement - * @param {object} nextElement - * @internal - */ - updateComponent: function(transaction, prevElement, nextElement, context) { - if (__DEV__) { - ReactElementValidator.checkAndWarnForMutatedProps(nextElement); - } - }, - - /** - * Get the publicly accessible representation of this component - i.e. what - * is exposed by refs and returned by React.render. Can be null for - * stateless components. - * - * @return {?ReactComponent} the actual sibling Component. - * @internal - */ - getPublicInstance: function() { - invariant( - false, - 'getPublicInstance should never be called on the base class. It must ' + - 'be overridden.' - ); - } - } -}; - -module.exports = ReactComponent; diff --git a/src/core/ReactCompositeComponent.js b/src/core/ReactCompositeComponent.js index fd9eed50a0..5d142e5981 100644 --- a/src/core/ReactCompositeComponent.js +++ b/src/core/ReactCompositeComponent.js @@ -11,17 +11,17 @@ 'use strict'; -var ReactComponent = require('ReactComponent'); var ReactComponentEnvironment = require('ReactComponentEnvironment'); var ReactContext = require('ReactContext'); var ReactCurrentOwner = require('ReactCurrentOwner'); var ReactElement = require('ReactElement'); +var ReactElementValidator = require('ReactElementValidator'); var ReactInstanceMap = require('ReactInstanceMap'); var ReactNativeComponent = require('ReactNativeComponent'); var ReactPerf = require('ReactPerf'); var ReactPropTypeLocations = require('ReactPropTypeLocations'); var ReactPropTypeLocationNames = require('ReactPropTypeLocationNames'); -var ReactRef = require('ReactRef'); +var ReactReconciler = require('ReactReconciler'); var ReactUpdates = require('ReactUpdates'); var assign = require('Object.assign'); @@ -97,8 +97,7 @@ var nextMountID = 1; /** * @lends {ReactCompositeComponent.prototype} */ -var ReactCompositeComponentMixin = assign({}, - ReactComponent.Mixin, { +var ReactCompositeComponentMixin = { /** * Base constructor for all composite component. @@ -108,6 +107,8 @@ var ReactCompositeComponentMixin = assign({}, * @internal */ construct: function(element) { + this._currentElement = element; + this._rootNodeID = null; this._instance = null; @@ -119,9 +120,6 @@ var ReactCompositeComponentMixin = assign({}, this._renderedComponent = null; - // Children can be either an array or more than one argument - ReactComponent.Mixin.construct.apply(this, arguments); - this._context = null; this._mountOrder = 0; this._isTopLevel = false; @@ -150,13 +148,6 @@ var ReactCompositeComponentMixin = assign({}, * @internal */ mountComponent: function(rootID, transaction, context) { - ReactComponent.Mixin.mountComponent.call( - this, - rootID, - transaction, - context - ); - this._context = context; this._mountOrder = nextMountID++; this._rootNodeID = rootID; @@ -253,7 +244,8 @@ var ReactCompositeComponentMixin = assign({}, // Done with mounting, `setState` will now trigger UI changes. this._compositeLifeCycleState = null; - var markup = this._renderedComponent.mountComponent( + var markup = ReactReconciler.mountComponent( + this._renderedComponent, rootID, transaction, this._processChildContext(context) @@ -261,16 +253,8 @@ var ReactCompositeComponentMixin = assign({}, if (inst.componentDidMount) { transaction.getReactMountReady().enqueue(inst.componentDidMount, inst); } - transaction.getReactMountReady().enqueue(this.attachRefs, this); - return markup; - }, - /** - * Helper to call ReactRef.attachRefs with this composite component, split out - * to avoid allocations in the transaction mount-ready queue. - */ - attachRefs: function() { - ReactRef.attachRefs(this, this._currentElement); + return markup; }, /** @@ -282,15 +266,13 @@ var ReactCompositeComponentMixin = assign({}, unmountComponent: function() { var inst = this._instance; - ReactRef.detachRefs(this, this._currentElement); - this._compositeLifeCycleState = CompositeLifeCycle.UNMOUNTING; if (inst.componentWillUnmount) { inst.componentWillUnmount(); } this._compositeLifeCycleState = null; - this._renderedComponent.unmountComponent(); + ReactReconciler.unmountComponent(this._renderedComponent); this._renderedComponent = null; // Reset pending fields @@ -299,8 +281,6 @@ var ReactCompositeComponentMixin = assign({}, this._pendingCallbacks = null; this._pendingElement = null; - ReactComponent.Mixin.unmountComponent.call(this); - ReactComponentEnvironment.unmountIDFromEnvironment(this._rootNodeID); this._context = null; @@ -636,22 +616,28 @@ var ReactCompositeComponentMixin = assign({}, } }, - receiveComponent: function(nextElement, transaction, context) { - if (nextElement === this._currentElement && - nextElement._owner != null) { - // Since elements are immutable after the owner is rendered, - // we can do a cheap identity compare here to determine if this is a - // superfluous reconcile. It's possible for state to be mutable but such - // change should trigger an update of the owner which would recreate - // the element. We explicitly check for the existence of an owner since - // it's possible for an element created outside a composite to be - // deeply mutated and reused. + receiveComponent: function(nextElement, transaction, nextContext) { + var compositeLifeCycleState = this._compositeLifeCycleState; + // Do not trigger a state transition if we are in the middle of mounting or + // receiving props because both of those will already be doing this. + if (compositeLifeCycleState === CompositeLifeCycle.MOUNTING || + compositeLifeCycleState === CompositeLifeCycle.RECEIVING_PROPS) { return; } - this._pendingElement = nextElement; - this._pendingContext = context; - this.performUpdateIfNecessary(transaction); + var prevElement = this._currentElement; + var prevContext = this._context; + + this._pendingElement = null; + this._pendingContext = null; + + this.updateComponent( + transaction, + prevElement, + nextElement, + prevContext, + nextContext + ); }, /** @@ -662,42 +648,30 @@ var ReactCompositeComponentMixin = assign({}, * @internal */ performUpdateIfNecessary: function(transaction) { - var compositeLifeCycleState = this._compositeLifeCycleState; - // Do not trigger a state transition if we are in the middle of mounting or - // receiving props because both of those will already be doing this. - if (compositeLifeCycleState === CompositeLifeCycle.MOUNTING || - compositeLifeCycleState === CompositeLifeCycle.RECEIVING_PROPS) { - return; + if (this._pendingElement != null || this._pendingContext != null) { + ReactReconciler.receiveComponent( + this, + this._pendingElement || this._currentElement, + transaction, + this._pendingContext || this._context + ); } - if (this._pendingElement == null && - this._pendingState == null && - this._pendingContext == null && - !this._pendingForceUpdate) { - return; - } + if (this._pendingState != null || this._pendingForceUpdate) { + if (__DEV__) { + ReactElementValidator.checkAndWarnForMutatedProps( + this._currentElement + ); + } - var prevElement = this._currentElement; - var nextElement = prevElement; - if (this._pendingElement != null) { - nextElement = this._pendingElement; - this._pendingElement = null; + this.updateComponent( + transaction, + this._currentElement, + this._currentElement, + this._context, + this._context + ); } - - var prevContext = this._context; - var nextContext = prevContext; - if (this._pendingContext != null) { - nextContext = this._pendingContext; - this._pendingContext = null; - } - - this.updateComponent( - transaction, - prevElement, - nextElement, - prevContext, - nextContext - ); }, /** @@ -746,15 +720,6 @@ var ReactCompositeComponentMixin = assign({}, prevUnmaskedContext, nextUnmaskedContext ) { - ReactComponent.Mixin.updateComponent.call( - this, - transaction, - prevParentElement, - nextParentElement, - prevUnmaskedContext, - nextUnmaskedContext - ); - var inst = this._instance; var prevContext = inst.context; @@ -762,16 +727,6 @@ var ReactCompositeComponentMixin = assign({}, var nextContext = prevContext; var nextProps = prevProps; - var refsChanged = ReactRef.shouldUpdateRefs( - this, - prevParentElement, - nextParentElement - ); - - if (refsChanged) { - ReactRef.detachRefs(this, prevParentElement); - } - // Distinguish between a props update versus a simple state update if (prevParentElement !== nextParentElement) { nextContext = this._processContext(nextParentElement._context); @@ -829,11 +784,6 @@ var ReactCompositeComponentMixin = assign({}, inst.state = nextState; inst.context = nextContext; } - - // Update refs regardless of what shouldComponentUpdate returns - if (refsChanged) { - transaction.getReactMountReady().enqueue(this.attachRefs, this); - } }, /** @@ -894,7 +844,8 @@ var ReactCompositeComponentMixin = assign({}, var prevRenderedElement = prevComponentInstance._currentElement; var nextRenderedElement = this._renderValidatedComponent(); if (shouldUpdateReactComponent(prevRenderedElement, nextRenderedElement)) { - prevComponentInstance.receiveComponent( + ReactReconciler.receiveComponent( + prevComponentInstance, nextRenderedElement, transaction, this._processChildContext(context) @@ -903,13 +854,14 @@ var ReactCompositeComponentMixin = assign({}, // These two IDs are actually the same! But nothing should rely on that. var thisID = this._rootNodeID; var prevComponentID = prevComponentInstance._rootNodeID; - prevComponentInstance.unmountComponent(); + ReactReconciler.unmountComponent(prevComponentInstance); this._renderedComponent = this._instantiateReactComponent( nextRenderedElement, this._currentElement.type ); - var nextMarkup = this._renderedComponent.mountComponent( + var nextMarkup = ReactReconciler.mountComponent( + this._renderedComponent, thisID, transaction, context @@ -957,7 +909,6 @@ var ReactCompositeComponentMixin = assign({}, this._currentElement._context ); ReactCurrentOwner.current = this; - var inst = this._instance; try { renderedComponent = this._renderValidatedComponentWithoutOwnerOrContext(); @@ -1033,7 +984,7 @@ var ReactCompositeComponentMixin = assign({}, // Stub _instantiateReactComponent: null -}); +}; ReactPerf.measureMethods( ReactCompositeComponentMixin, diff --git a/src/core/ReactMultiChild.js b/src/core/ReactMultiChild.js index 758292125e..129fb43198 100644 --- a/src/core/ReactMultiChild.js +++ b/src/core/ReactMultiChild.js @@ -15,6 +15,7 @@ var ReactComponentEnvironment = require('ReactComponentEnvironment'); var ReactMultiChildUpdateTypes = require('ReactMultiChildUpdateTypes'); +var ReactReconciler = require('ReactReconciler'); var ReactChildReconciler = require('ReactChildReconciler'); /** @@ -184,11 +185,12 @@ var ReactMultiChild = { var mountImages = []; var index = 0; for (var name in children) { - var child = children[name]; if (children.hasOwnProperty(name)) { + var child = children[name]; // Inlined for performance, see `ReactInstanceHandles.createReactID`. var rootID = this._rootNodeID + name; - var mountImage = child.mountComponent( + var mountImage = ReactReconciler.mountComponent( + child, rootID, transaction, context @@ -395,7 +397,8 @@ var ReactMultiChild = { context) { // Inlined for performance, see `ReactInstanceHandles.createReactID`. var rootID = this._rootNodeID + name; - var mountImage = child.mountComponent( + var mountImage = ReactReconciler.mountComponent( + child, rootID, transaction, context diff --git a/src/core/ReactReconciler.js b/src/core/ReactReconciler.js new file mode 100644 index 0000000000..5d2f93865c --- /dev/null +++ b/src/core/ReactReconciler.js @@ -0,0 +1,107 @@ +/** + * Copyright 2013-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 ReactReconciler + */ + +'use strict'; + +var ReactRef = require('ReactRef'); +var ReactElementValidator = require('ReactElementValidator'); + +/** + * Helper to call ReactRef.attachRefs with this composite component, split out + * to avoid allocations in the transaction mount-ready queue. + */ +function attachRefs() { + ReactRef.attachRefs(this, this._currentElement); +} + +var ReactReconciler = { + + /** + * Initializes the component, renders markup, and registers event listeners. + * + * @param {ReactComponent} internalInstance + * @param {string} rootID DOM ID of the root node. + * @param {ReactReconcileTransaction|ReactServerRenderingTransaction} transaction + * @return {?string} Rendered markup to be inserted into the DOM. + * @final + * @internal + */ + mountComponent: function(internalInstance, rootID, transaction, context) { + var markup = internalInstance.mountComponent(rootID, transaction, context); + if (__DEV__) { + ReactElementValidator.checkAndWarnForMutatedProps( + internalInstance._currentElement + ); + } + transaction.getReactMountReady().enqueue(attachRefs, internalInstance); + return markup; + }, + + /** + * Releases any resources allocated by `mountComponent`. + * + * @final + * @internal + */ + unmountComponent: function(internalInstance) { + ReactRef.detachRefs(internalInstance, internalInstance._currentElement); + internalInstance.unmountComponent(); + }, + + /** + * Update a component using a new element. + * + * @param {ReactComponent} internalInstance + * @param {ReactElement} nextElement + * @param {ReactReconcileTransaction} transaction + * @param {object} context + * @internal + */ + receiveComponent: function( + internalInstance, nextElement, transaction, context + ) { + var prevElement = internalInstance._currentElement; + + if (nextElement === prevElement && nextElement._owner != null) { + // Since elements are immutable after the owner is rendered, + // we can do a cheap identity compare here to determine if this is a + // superfluous reconcile. It's possible for state to be mutable but such + // change should trigger an update of the owner which would recreate + // the element. We explicitly check for the existence of an owner since + // it's possible for an element created outside a composite to be + // deeply mutated and reused. + return; + } + + if (__DEV__) { + ReactElementValidator.checkAndWarnForMutatedProps(nextElement); + } + + var refsChanged = ReactRef.shouldUpdateRefs( + this, + prevElement, + nextElement + ); + + if (refsChanged) { + ReactRef.detachRefs(internalInstance, prevElement); + } + + internalInstance.receiveComponent(nextElement, transaction, context); + + if (refsChanged) { + transaction.getReactMountReady().enqueue(attachRefs, internalInstance); + } + } + +}; + +module.exports = ReactReconciler; diff --git a/src/core/__tests__/ReactComponentLifeCycle-test.js b/src/core/__tests__/ReactComponentLifeCycle-test.js index d91142ad1c..936313905c 100644 --- a/src/core/__tests__/ReactComponentLifeCycle-test.js +++ b/src/core/__tests__/ReactComponentLifeCycle-test.js @@ -15,7 +15,6 @@ var keyMirror = require('keyMirror'); var React; var ReactTestUtils; -var ReactComponent; var ReactCompositeComponent; var ReactInstanceMap; var CompositeComponentLifeCycle; @@ -97,7 +96,6 @@ describe('ReactComponentLifeCycle', function() { require('mock-modules').dumpCache(); React = require('React'); ReactTestUtils = require('ReactTestUtils'); - ReactComponent = require('ReactComponent'); ReactCompositeComponent = require('ReactCompositeComponent'); CompositeComponentLifeCycle = ReactCompositeComponent.LifeCycle; diff --git a/src/core/__tests__/ReactCompositeComponent-test.js b/src/core/__tests__/ReactCompositeComponent-test.js index 9e44e9e6bc..ee705d4e2f 100644 --- a/src/core/__tests__/ReactCompositeComponent-test.js +++ b/src/core/__tests__/ReactCompositeComponent-test.js @@ -14,7 +14,6 @@ var ChildUpdates; var MorphingComponent; var React; -var ReactComponent; var ReactCurrentOwner; var ReactDoNotBindDeprecated; var ReactMount; @@ -36,7 +35,6 @@ describe('ReactCompositeComponent', function() { reactComponentExpect = require('reactComponentExpect'); React = require('React'); - ReactComponent = require('ReactComponent'); ReactCurrentOwner = require('ReactCurrentOwner'); ReactDoNotBindDeprecated = require('ReactDoNotBindDeprecated'); ReactPropTypes = require('ReactPropTypes'); diff --git a/src/core/__tests__/ReactMockedComponent-test.js b/src/core/__tests__/ReactMockedComponent-test.js index cb8a4001fa..4d558e916a 100644 --- a/src/core/__tests__/ReactMockedComponent-test.js +++ b/src/core/__tests__/ReactMockedComponent-test.js @@ -12,7 +12,6 @@ 'use strict'; var React; -var ReactComponent; var ReactTestUtils; var mocks; @@ -27,7 +26,6 @@ describe('ReactMockedComponent', function() { mocks = require('mocks'); React = require('React'); - ReactComponent = require('ReactComponent'); ReactTestUtils = require('ReactTestUtils'); OriginalComponent = React.createClass({ diff --git a/src/core/instantiateReactComponent.js b/src/core/instantiateReactComponent.js index da6fc46c2d..05b8696358 100644 --- a/src/core/instantiateReactComponent.js +++ b/src/core/instantiateReactComponent.js @@ -108,6 +108,12 @@ function instantiateReactComponent(node, parentCompositeType) { // Sets up the instance. This can probably just move into the constructor now. instance.construct(node); + // These two fields are used by the DOM and ART diffing algorithms + // respectively. Instead of using expandos on components, we should be + // storing the state needed by the diffing algorithms elsewhere. + instance._mountIndex = 0; + instance._mountImage = null; + // Internal instances should fully constructed at this point, so they should // not get any new fields added to them at this point. if (__DEV__) {