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 {