@samitouri / QOS-React-2 / commits / 3ea3274ca4

Clone on mount

This is the first step towards descriptors. This will start cloning the component when it's mounted instead of mounting the first instance. This avoids an issue where a reference to the first instance can hang around in props. Since a mounted component gets mutated, the descriptor changes. We don't need to clone the props object itself. Mutating the shallow props object of a child that's passed into you is already flawed. Those cases need to use cloneWithProps. A props object is considered shallow frozen after it leaves the render it was created in.

Sebastian Markbage committed Mar 10, 2014 at 15:26 UTC 3ea3274ca48d238ed730fe5924e0417841ba26f7
11 files changed +312 -102
src/browser/ReactTextComponent.js
+10
@@ -45,6 +45,16 @@ var ReactTextComponent = function(initialText) {
45 this.construct({text: initialText});
46 };
47
48 +/**
49 + * Used to clone the text descriptor object before it's mounted.
50 + *
51 + * @param {object} props
52 + * @return {object} A new ReactTextComponent instance
53 + */
54 +ReactTextComponent.ConvenienceConstructor = function(props) {
55 + return new ReactTextComponent(props.text);
56 +};
57 +
58 mixInto(ReactTextComponent, ReactComponent.Mixin);
59 mixInto(ReactTextComponent, ReactBrowserComponentMixin);
60 mixInto(ReactTextComponent, {
src/browser/server/ReactServerRendering.js
+3 -2
@@ -24,7 +24,7 @@ var ReactMarkupChecksum = require('ReactMarkupChecksum');
24 var ReactServerRenderingTransaction =
25 require('ReactServerRenderingTransaction');
26
27 -
27 +var instantiateReactComponent = require('instantiateReactComponent');
28 var invariant = require('invariant');
29
30 /**
@@ -49,7 +49,8 @@ function renderComponentToString(component) {
49 transaction = ReactServerRenderingTransaction.getPooled(false);
50
51 return transaction.perform(function() {
52 - var markup = component.mountComponent(id, transaction, 0);
52 + var componentInstance = instantiateReactComponent(component);
53 + var markup = componentInstance.mountComponent(id, transaction, 0);
54 return ReactMarkupChecksum.addChecksumToMarkup(markup);
55 }, null);
56 } finally {
src/browser/ui/ReactMount.js
+9 -3
@@ -25,6 +25,7 @@ var ReactPerf = require('ReactPerf');
25
26 var containsNode = require('containsNode');
27 var getReactRootElementInContainer = require('getReactRootElementInContainer');
28 +var instantiateReactComponent = require('instantiateReactComponent');
29 var invariant = require('invariant');
30 var shouldUpdateReactComponent = require('shouldUpdateReactComponent');
31
@@ -296,8 +297,13 @@ var ReactMount = {
297 nextComponent,
298 container,
299 shouldReuseMarkup) {
299 - var reactRootID = ReactMount._registerComponent(nextComponent, container);
300 - nextComponent.mountComponentIntoNode(
300 +
301 + var componentInstance = instantiateReactComponent(nextComponent);
302 + var reactRootID = ReactMount._registerComponent(
303 + componentInstance,
304 + container
305 + );
306 + componentInstance.mountComponentIntoNode(
307 reactRootID,
308 container,
309 shouldReuseMarkup
@@ -309,7 +315,7 @@ var ReactMount = {
315 getReactRootElementInContainer(container);
316 }
317
312 - return nextComponent;
318 + return componentInstance;
319 }
320 ),
321
src/core/ReactCompositeComponent.js
+168 -54
@@ -29,6 +29,7 @@ var ReactPropTypeLocations = require('ReactPropTypeLocations');
29 var ReactPropTypeLocationNames = require('ReactPropTypeLocationNames');
30 var ReactUpdates = require('ReactUpdates');
31
32 +var instantiateReactComponent = require('instantiateReactComponent');
33 var invariant = require('invariant');
34 var keyMirror = require('keyMirror');
35 var merge = require('merge');
@@ -595,19 +596,52 @@ if (__DEV__) {
596 constructor: true,
597 construct: true,
598 isOwnedBy: true, // should be deprecated but can have code mod (internal)
598 - mountComponent: true,
599 - mountComponentIntoNode: true,
600 - props: true,
599 type: true,
602 - _checkPropTypes: true,
603 - _mountComponentIntoNode: true,
604 - _processContext: true
600 + props: true,
601 + // currently private but belong on the descriptor and are valid for use
602 + // inside the framework:
603 + __keyValidated__: true,
604 + _owner: true,
605 + _currentContext: true
606 + };
607 +
608 + var componentInstanceProperties = {
609 + __keyValidated__: true,
610 + __keySetters: true,
611 + _compositeLifeCycleState: true,
612 + _currentContext: true,
613 + _defaultProps: true,
614 + _instance: true,
615 + _lifeCycleState: true,
616 + _mountDepth: true,
617 + _owner: true,
618 + _pendingCallbacks: true,
619 + _pendingContext: true,
620 + _pendingForceUpdate: true,
621 + _pendingOwner: true,
622 + _pendingProps: true,
623 + _pendingState: true,
624 + _renderedComponent: true,
625 + _rootNodeID: true,
626 + context: true,
627 + props: true,
628 + refs: true,
629 + state: true,
630 +
631 + // These are known instance properties coming from other sources
632 + _pendingQueries: true,
633 + _queryPropListeners: true,
634 + queryParams: true
635 +
636 };
637
638 var hasWarnedOnComponentType = {};
639
609 - var warnIfUnmounted = function(instance, key) {
610 - if (instance.__hasBeenMounted) {
640 + var warningStackCounter = 0;
641 +
642 + var issueMembraneWarning = function(instance, key) {
643 + var isWhitelisted = unmountedPropertyWhitelist.hasOwnProperty(key);
644 + if (warningStackCounter > 0 || isWhitelisted) {
645 return;
646 }
647 var name = instance.constructor.displayName || 'Unknown';
@@ -631,6 +665,30 @@ if (__DEV__) {
665 );
666 };
667
668 + var wrapInMembraneFunction = function(fn, thisBinding) {
669 + if (fn.__reactMembraneFunction && fn.__reactMembraneSelf === thisBinding) {
670 + return fn.__reactMembraneFunction;
671 + }
672 + return fn.__reactMembraneFunction = function() {
673 + /**
674 + * By getting this function, you've already received a warning. The
675 + * internals of this function will likely cause more warnings. To avoid
676 + * Spamming too much we disable any warning triggered inside of this
677 + * stack.
678 + */
679 + warningStackCounter++;
680 + try {
681 + // If the this binding is unchanged, we defer to the real component.
682 + // This is important to keep some referential integrity in the
683 + // internals. E.g. owner equality check.
684 + var self = this === thisBinding ? this.__realComponentInstance : this;
685 + return fn.apply(self, arguments);
686 + } finally {
687 + warningStackCounter--;
688 + }
689 + };
690 + };
691 +
692 var defineMembraneProperty = function(membrane, prototype, key) {
693 Object.defineProperty(membrane, key, {
694
@@ -638,31 +696,33 @@ if (__DEV__) {
696 enumerable: true,
697
698 get: function() {
641 - if (this !== membrane) {
642 - // When this is accessed through a prototype chain we need to check if
643 - // this component was mounted.
644 - warnIfUnmounted(this, key);
699 + if (this === membrane) {
700 + // We're allowed to access the prototype directly.
701 + return prototype[key];
702 + }
703 + issueMembraneWarning(this, key);
704 +
705 + var realValue = this.__realComponentInstance[key];
706 + // If the real value is a function, we need to provide a wrapper that
707 + // disables nested warnings. The properties type and constructors are
708 + // expected to the be constructors and therefore is often use with an
709 + // equality check and we shouldn't try to rebind those.
710 + if (typeof realValue === 'function' &&
711 + key !== 'type' &&
712 + key !== 'constructor') {
713 + return wrapInMembraneFunction(realValue, this);
714 }
646 - return prototype[key];
715 + return realValue;
716 },
717
718 set: function(value) {
650 - if (this !== membrane) {
651 - // When this is accessed through a prototype chain, we first check if
652 - // this component was mounted. Then we define a value on "this"
653 - // instance, effectively disabling the membrane on that prototype
654 - // chain.
655 - warnIfUnmounted(this, key);
656 - Object.defineProperty(this, key, {
657 - enumerable: true,
658 - configurable: true,
659 - writable: true,
660 - value: value
661 - });
662 - } else {
663 - // Otherwise, this should modify the prototype
719 + if (this === membrane) {
720 + // We're allowed to set a value on the prototype directly.
721 prototype[key] = value;
722 + return;
723 }
724 + issueMembraneWarning(this, key);
725 + this.__realComponentInstance[key] = value;
726 }
727
728 });
@@ -677,26 +737,51 @@ if (__DEV__) {
737 * @private
738 */
739 var createMountWarningMembrane = function(prototype) {
680 - try {
681 - var membrane = Object.create(prototype);
682 - for (var key in prototype) {
683 - if (unmountedPropertyWhitelist.hasOwnProperty(key)) {
684 - continue;
685 - }
740 + var membrane = {};
741 + var key;
742 + for (key in prototype) {
743 + defineMembraneProperty(membrane, prototype, key);
744 + }
745 + // These are properties that goes into the instance but not the prototype.
746 + // We can create the membrane on the prototype even though this will
747 + // result in a faulty hasOwnProperty check it's better perf.
748 + for (key in componentInstanceProperties) {
749 + if (componentInstanceProperties.hasOwnProperty(key) &&
750 + !(key in prototype)) {
751 defineMembraneProperty(membrane, prototype, key);
752 }
753 + }
754 + return membrane;
755 + };
756
689 - membrane.mountComponent = function() {
690 - this.__hasBeenMounted = true;
691 - return prototype.mountComponent.apply(this, arguments);
757 + /**
758 + * Creates a membrane constructor which wraps the component that gets mounted.
759 + *
760 + * @param {function} constructor Original constructor.
761 + * @return {function} The membrane constructor.
762 + * @private
763 + */
764 + var createDescriptorProxy = function(constructor) {
765 + try {
766 + var ProxyConstructor = function() {
767 + this.__realComponentInstance = new constructor();
768 +
769 + // We can only safely pass through known instance variables. Unknown
770 + // expandos are not safe. Use the real mounted instance to avoid this
771 + // problem if it blows something up.
772 + Object.freeze(this);
773 };
774
694 - return membrane;
775 + ProxyConstructor.prototype = createMountWarningMembrane(
776 + constructor.prototype
777 + );
778 +
779 + return ProxyConstructor;
780 } catch(x) {
781 // In IE8 define property will fail on non-DOM objects. If anything in
697 - // the membrane creation fails, we'll bail out and just use the prototype
698 - // without warnings.
699 - return prototype;
782 + // the membrane creation fails, we'll bail out and just use the plain
783 + // constructor without warnings.
784 + return constructor;
785 }
786 };
787
@@ -772,13 +857,29 @@ var ReactCompositeComponentMixin = {
857 this.state = null;
858 this._pendingState = null;
859
775 - this.context = this._processContext(ReactContext.current);
860 + this.context = null;
861 this._currentContext = ReactContext.current;
862 this._pendingContext = null;
863
864 + // The descriptor that was used to instantiate this component. Will be
865 + // set by the instantiator instead of the constructor since this
866 + // constructor is currently used by both instances and descriptors.
867 + this._descriptor = null;
868 +
869 this._compositeLifeCycleState = null;
870 },
871
872 + /**
873 + * Components in the intermediate state now has cyclic references. To avoid
874 + * breaking JSON serialization we expose a custom JSON format.
875 + * @return {object} JSON compatible representation.
876 + * @internal
877 + * @final
878 + */
879 + toJSON: function() {
880 + return { type: this.type, props: this.props };
881 + },
882 +
883 /**
884 * Checks whether or not this composite component is mounted.
885 * @return {boolean} True if mounted, false otherwise.
@@ -812,6 +913,7 @@ var ReactCompositeComponentMixin = {
913 );
914 this._compositeLifeCycleState = CompositeLifeCycle.MOUNTING;
915
916 + this.context = this._processContext(this._currentContext);
917 this._defaultProps = this.getDefaultProps ? this.getDefaultProps() : null;
918 this.props = this._processProps(this.props);
919
@@ -839,7 +941,9 @@ var ReactCompositeComponentMixin = {
941 }
942 }
943
842 - this._renderedComponent = this._renderValidatedComponent();
944 + this._renderedComponent = instantiateReactComponent(
945 + this._renderValidatedComponent()
946 + );
947
948 // Done with mounting, `setState` will now trigger UI changes.
949 this._compositeLifeCycleState = null;
@@ -1173,13 +1277,16 @@ var ReactCompositeComponentMixin = {
1277 },
1278
1279 receiveComponent: function(nextComponent, transaction) {
1176 - if (nextComponent === this) {
1280 + if (nextComponent === this._descriptor) {
1281 // Since props and context are immutable after the component is
1282 // mounted, we can do a cheap identity compare here to determine
1283 // if this is a superfluous reconcile.
1284 return;
1285 }
1286
1287 + // Update the descriptor that was last used by this component instance
1288 + this._descriptor = nextComponent;
1289 +
1290 this._pendingContext = nextComponent._currentContext;
1291 ReactComponent.Mixin.receiveComponent.call(
1292 this,
@@ -1212,17 +1319,19 @@ var ReactCompositeComponentMixin = {
1319 prevProps,
1320 prevOwner
1321 );
1215 - var prevComponent = this._renderedComponent;
1322 +
1323 +
1324 + var prevComponentInstance = this._renderedComponent;
1325 var nextComponent = this._renderValidatedComponent();
1217 - if (shouldUpdateReactComponent(prevComponent, nextComponent)) {
1218 - prevComponent.receiveComponent(nextComponent, transaction);
1326 + if (shouldUpdateReactComponent(prevComponentInstance, nextComponent)) {
1327 + prevComponentInstance.receiveComponent(nextComponent, transaction);
1328 } else {
1329 // These two IDs are actually the same! But nothing should rely on that.
1330 var thisID = this._rootNodeID;
1222 - var prevComponentID = prevComponent._rootNodeID;
1223 - prevComponent.unmountComponent();
1224 - this._renderedComponent = nextComponent;
1225 - var nextMarkup = nextComponent.mountComponent(
1331 + var prevComponentID = prevComponentInstance._rootNodeID;
1332 + prevComponentInstance.unmountComponent();
1333 + this._renderedComponent = instantiateReactComponent(nextComponent);
1334 + var nextMarkup = this._renderedComponent.mountComponent(
1335 thisID,
1336 transaction,
1337 this._mountDepth + 1
@@ -1401,10 +1510,12 @@ var ReactCompositeComponent = {
1510 Constructor.prototype = new ReactCompositeComponentBase();
1511 Constructor.prototype.constructor = Constructor;
1512
1513 + var DescriptorConstructor = Constructor;
1514 +
1515 var ConvenienceConstructor = function(props, children) {
1405 - var instance = new Constructor();
1406 - instance.construct.apply(instance, arguments);
1407 - return instance;
1516 + var descriptor = new DescriptorConstructor();
1517 + descriptor.construct.apply(descriptor, arguments);
1518 + return descriptor;
1519 };
1520 ConvenienceConstructor.componentConstructor = Constructor;
1521 Constructor.ConvenienceConstructor = ConvenienceConstructor;
@@ -1452,7 +1563,10 @@ var ReactCompositeComponent = {
1563 }
1564
1565 if (__DEV__) {
1455 - Constructor.prototype = createMountWarningMembrane(Constructor.prototype);
1566 + // In DEV the convenience constructor generates a proxy to another
1567 + // instance around it to warn about access to properties on the
1568 + // descriptor.
1569 + DescriptorConstructor = createDescriptorProxy(Constructor);
1570 }
1571
1572 return ConvenienceConstructor;
src/core/ReactMultiChild.js
+10 -3
@@ -23,6 +23,7 @@ var ReactComponent = require('ReactComponent');
23 var ReactMultiChildUpdateTypes = require('ReactMultiChildUpdateTypes');
24
25 var flattenChildren = require('flattenChildren');
26 +var instantiateReactComponent = require('instantiateReactComponent');
27 var shouldUpdateReactComponent = require('shouldUpdateReactComponent');
28
29 /**
@@ -192,14 +193,18 @@ var ReactMultiChild = {
193 for (var name in children) {
194 var child = children[name];
195 if (children.hasOwnProperty(name)) {
196 + // The rendered children must be turned into instances as they're
197 + // mounted.
198 + var childInstance = instantiateReactComponent(child);
199 + children[name] = childInstance;
200 // Inlined for performance, see `ReactInstanceHandles.createReactID`.
201 var rootID = this._rootNodeID + name;
197 - var mountImage = child.mountComponent(
202 + var mountImage = childInstance.mountComponent(
203 rootID,
204 transaction,
205 this._mountDepth + 1
206 );
202 - child._mountIndex = index;
207 + childInstance._mountIndex = index;
208 mountImages.push(mountImage);
209 index++;
210 }
@@ -293,8 +298,10 @@ var ReactMultiChild = {
298 lastIndex = Math.max(prevChild._mountIndex, lastIndex);
299 this._unmountChildByName(prevChild, name);
300 }
301 + // The child must be instantiated before it's mounted.
302 + var nextChildInstance = instantiateReactComponent(nextChild);
303 this._mountChildByNameAtIndex(
297 - nextChild, name, nextIndex, transaction
304 + nextChildInstance, name, nextIndex, transaction
305 );
306 }
307 nextIndex++;
src/core/__tests__/ReactBind-test.js
+11 -11
@@ -61,13 +61,13 @@ describe('autobinding', function() {
61 });
62
63 var instance1 = <TestBindComponent />;
64 - ReactTestUtils.renderIntoDocument(instance1);
64 + var mountedInstance1 = ReactTestUtils.renderIntoDocument(instance1);
65 var rendered1 = reactComponentExpect(instance1)
66 .expectRenderedChild()
67 .instance();
68
69 var instance2 = <TestBindComponent />;
70 - ReactTestUtils.renderIntoDocument(instance2);
70 + var mountedInstance2 = ReactTestUtils.renderIntoDocument(instance2);
71 var rendered2 = reactComponentExpect(instance2)
72 .expectRenderedChild()
73 .instance();
@@ -77,25 +77,25 @@ describe('autobinding', function() {
77 badIdea();
78 }).toThrow();
79
80 - expect(instance1.onMouseEnter).toBe(instance2.onMouseEnter);
81 - expect(instance1.onMouseLeave).toBe(instance2.onMouseLeave);
82 - expect(instance1.onClick).not.toBe(instance2.onClick);
80 + expect(mountedInstance1.onMouseEnter).toBe(mountedInstance2.onMouseEnter);
81 + expect(mountedInstance1.onMouseLeave).toBe(mountedInstance2.onMouseLeave);
82 + expect(mountedInstance1.onClick).not.toBe(mountedInstance2.onClick);
83
84 ReactTestUtils.Simulate.click(rendered1);
85 expect(mouseDidClick.mock.instances.length).toBe(1);
86 - expect(mouseDidClick.mock.instances[0]).toBe(instance1);
86 + expect(mouseDidClick.mock.instances[0]).toBe(mountedInstance1);
87
88 ReactTestUtils.Simulate.click(rendered2);
89 expect(mouseDidClick.mock.instances.length).toBe(2);
90 - expect(mouseDidClick.mock.instances[1]).toBe(instance2);
90 + expect(mouseDidClick.mock.instances[1]).toBe(mountedInstance2);
91
92 ReactTestUtils.Simulate.mouseOver(rendered1);
93 expect(mouseDidEnter.mock.instances.length).toBe(1);
94 - expect(mouseDidEnter.mock.instances[0]).toBe(instance1);
94 + expect(mouseDidEnter.mock.instances[0]).toBe(mountedInstance1);
95
96 ReactTestUtils.Simulate.mouseOver(rendered2);
97 expect(mouseDidEnter.mock.instances.length).toBe(2);
98 - expect(mouseDidEnter.mock.instances[1]).toBe(instance2);
98 + expect(mouseDidEnter.mock.instances[1]).toBe(mountedInstance2);
99
100 ReactTestUtils.Simulate.mouseOut(rendered1);
101 expect(mouseDidLeave.mock.instances.length).toBe(1);
@@ -122,14 +122,14 @@ describe('autobinding', function() {
122 });
123
124 var instance1 = <TestBindComponent />;
125 - ReactTestUtils.renderIntoDocument(instance1);
125 + var mountedInstance1 = ReactTestUtils.renderIntoDocument(instance1);
126 var rendered1 = reactComponentExpect(instance1)
127 .expectRenderedChild()
128 .instance();
129
130 ReactTestUtils.Simulate.click(rendered1);
131 expect(mouseDidClick.mock.instances.length).toBe(1);
132 - expect(mouseDidClick.mock.instances[0]).toBe(instance1);
132 + expect(mouseDidClick.mock.instances[0]).toBe(mountedInstance1);
133 });
134
135 });
src/core/__tests__/ReactComponentLifeCycle-test.js
+1 -3
@@ -312,9 +312,7 @@ describe('ReactComponentLifeCycle', function() {
312 // A component that is merely "constructed" (as in "constructor") but not
313 // yet initialized, or rendered.
314 //
315 - var instance = <LifeCycleComponent />;
316 - expect(instance._lifeCycleState).toBe(ComponentLifeCycle.UNMOUNTED);
317 - ReactTestUtils.renderIntoDocument(instance);
315 + var instance = ReactTestUtils.renderIntoDocument(<LifeCycleComponent />);
316
317 // getInitialState
318 expect(instance._testJournal.returnedFromGetInitialState).toEqual(
src/core/__tests__/ReactCompositeComponent-test.js
+11 -11
@@ -183,21 +183,23 @@ describe('ReactCompositeComponent', function() {
183
184 // Next, prove that once mounted, the scope is bound correctly to the actual
185 // component.
186 - ReactTestUtils.renderIntoDocument(instance);
186 + var mountedInstance = ReactTestUtils.renderIntoDocument(instance);
187 expect(console.warn.argsForCall.length).toBe(3);
188 + // This will result in a warning that has already been issued before.
189 var explicitlyBound = instance.methodToBeExplicitlyBound.bind(instance);
189 - expect(console.warn.argsForCall.length).toBe(4);
190 + expect(console.warn.argsForCall.length).toBe(3);
191 var autoBound = instance.methodAutoBound;
192 var explicitlyNotBound = instance.methodExplicitlyNotBound;
193
194 var context = {};
194 - expect(explicitlyBound.call(context)).toBe(instance);
195 - expect(autoBound.call(context)).toBe(instance);
195 + expect(explicitlyBound.call(context)).toBe(mountedInstance);
196 + expect(autoBound.call(context)).toBe(mountedInstance);
197 expect(explicitlyNotBound.call(context)).toBe(context);
198
198 - expect(explicitlyBound.call(instance)).toBe(instance);
199 - expect(autoBound.call(instance)).toBe(instance);
200 - expect(explicitlyNotBound.call(instance)).toBe(instance);
199 + expect(explicitlyBound.call(instance)).toBe(mountedInstance);
200 + expect(autoBound.call(instance)).toBe(mountedInstance);
201 + // This one is the weird one
202 + expect(explicitlyNotBound.call(instance)).toBe(mountedInstance);
203
204 });
205
@@ -279,8 +281,7 @@ describe('ReactCompositeComponent', function() {
281 }
282 });
283
282 - var instance = <Component />;
283 - ReactTestUtils.renderIntoDocument(instance);
284 + var instance = ReactTestUtils.renderIntoDocument(<Component />);
285 reactComponentExpect(instance).scalarPropsEqual({key: 'testKey'});
286 reactComponentExpect(instance).scalarStateEqual({key: 'testKeyState'});
287
@@ -1179,9 +1180,8 @@ describe('ReactCompositeComponent', function() {
1180 expect(console.warn.argsForCall.length).toBe(0);
1181
1182 var unmountedInstance = <ComponentClass />;
1182 - var result = unmountedInstance.someMethod();
1183 + unmountedInstance.someMethod();
1184 expect(console.warn.argsForCall.length).toBe(1);
1184 - expect(result).toBe(unmountedInstance);
1185
1186 var unmountedInstance2 = <ComponentClass />;
1187 unmountedInstance2.someOtherMethod = 'override';
src/core/instantiateReactComponent.js new
+70
@@ -0,0 +1,70 @@
1 +/**
2 + * Copyright 2013-2014 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + * @providesModule instantiateReactComponent
17 + * @typechecks static-only
18 + */
19 +
20 +"use strict";
21 +
22 +var warning = require('warning');
23 +
24 +/**
25 + * Validate a `componentDescriptor`. This should be exposed publicly in a follow
26 + * up diff.
27 + *
28 + * @param {object} descriptor
29 + * @return {boolean} Returns true if this is a valid descriptor of a Component.
30 + */
31 +function isValidComponentDescriptor(descriptor) {
32 + return (
33 + typeof descriptor.constructor === 'function' &&
34 + typeof descriptor.constructor.prototype.construct === 'function' &&
35 + typeof descriptor.constructor.prototype.mountComponent === 'function' &&
36 + typeof descriptor.constructor.prototype.receiveComponent === 'function'
37 + );
38 +}
39 +
40 +/**
41 + * Given a `componentDescriptor` create an instance that will actually be
42 + * mounted. Currently it just extracts an existing clone from composite
43 + * components but this is an implementation detail which will change.
44 + *
45 + * @param {object} descriptor
46 + * @return {object} A new instance of componentDescriptor's constructor.
47 + * @protected
48 + */
49 +function instantiateReactComponent(descriptor) {
50 + if (__DEV__) {
51 + warning(
52 + isValidComponentDescriptor(descriptor),
53 + 'Only React Components are valid for mounting.'
54 + );
55 + // We use the clone of a composite component instead of the original
56 + // instance. This allows us to warn you if you're are accessing the wrong
57 + // instance.
58 + var instance = descriptor.__realComponentInstance || descriptor;
59 + instance._descriptor = descriptor;
60 + return instance;
61 + }
62 + // In prod we don't clone, we simply use the same instance for unaffected
63 + // behavior. We have to keep the descriptor around for comparison later on.
64 + // This should ideally be accepted in the constructor of the instance but
65 + // since that is currently overloaded, we just manually attach it here.
66 + descriptor._descriptor = descriptor;
67 + return descriptor;
68 +}
69 +
70 +module.exports = instantiateReactComponent;
src/core/shouldUpdateReactComponent.js
+17 -14
@@ -20,33 +20,36 @@
20 "use strict";
21
22 /**
23 - * Given a `prevComponent` and `nextComponent`, determines if `prevComponent`
24 - * should be updated as opposed to being destroyed or replaced.
23 + * Given a `prevComponentInstance` and `nextComponent`, determines if
24 + * `prevComponentInstance` should be updated as opposed to being destroyed or
25 + * replaced by a new instance. The second argument is a descriptor. Future
26 + * versions of the reconciler should only compare descriptors to other
27 + * descriptors.
28 *
26 - * @param {?object} prevComponent
27 - * @param {?object} nextComponent
28 - * @return {boolean} True if `prevComponent` should be updated.
29 + * @param {?object} prevComponentInstance
30 + * @param {?object} nextDescriptor
31 + * @return {boolean} True if `prevComponentInstance` should be updated.
32 * @protected
33 */
31 -function shouldUpdateReactComponent(prevComponent, nextComponent) {
34 +function shouldUpdateReactComponent(prevComponentInstance, nextDescriptor) {
35 // TODO: Remove warning after a release.
33 - if (prevComponent && nextComponent &&
34 - prevComponent.constructor === nextComponent.constructor && (
35 - (prevComponent.props && prevComponent.props.key) ===
36 - (nextComponent.props && nextComponent.props.key)
36 + if (prevComponentInstance && nextDescriptor &&
37 + prevComponentInstance.constructor === nextDescriptor.constructor && (
38 + (prevComponentInstance.props && prevComponentInstance.props.key) ===
39 + (nextDescriptor.props && nextDescriptor.props.key)
40 )) {
38 - if (prevComponent._owner === nextComponent._owner) {
41 + if (prevComponentInstance._owner === nextDescriptor._owner) {
42 return true;
43 } else {
44 if (__DEV__) {
42 - if (prevComponent.state) {
45 + if (prevComponentInstance.state) {
46 console.warn(
47 'A recent change to React has been found to impact your code. ' +
48 'A mounted component will now be unmounted and replaced by a ' +
49 'component (of the same class) if their owners are different. ' +
50 'Previously, ownership was not considered when updating.',
48 - prevComponent,
49 - nextComponent
51 + prevComponentInstance,
52 + nextDescriptor
53 );
54 }
55 }
src/utils/traverseAllChildren.js
+2 -1
@@ -126,7 +126,8 @@ var traverseAllChildrenImpl =
126 // All of the above are perceived as null.
127 callback(traverseContext, null, storageName, indexSoFar);
128 subtreeCount = 1;
129 - } else if (children.mountComponentIntoNode) {
129 + } else if (children.type && children.type.prototype &&
130 + children.type.prototype.mountComponentIntoNode) {
131 callback(traverseContext, children, storageName, indexSoFar);
132 subtreeCount = 1;
133 } else {