First phase to true descriptors
This moves all convenience constructors to use frozen ReactDescriptors.
Sebastian Markbage committed
Mar 28, 2014 at 11:54 UTC
c40e06f7281622a7a6295f4a7d5540a75fedf7bb
29 files changed
+758
-832
src/browser/ReactDOM.js
+5
-14
@@ -19,6 +19,7 @@
19
20
"use strict";
21
22
+var ReactDescriptor = require('ReactDescriptor');
23
var ReactDOMComponent = require('ReactDOMComponent');
24
25
var mergeInto = require('mergeInto');
@@ -40,25 +41,15 @@ var mapObject = require('mapObject');
41
* @private
42
*/
43
function createDOMComponentClass(omitClose, tag) {
43
- var Constructor = function() {};
44
+ var Constructor = function(descriptor) {
45
+ this.construct(descriptor);
46
+ };
47
Constructor.prototype = new ReactDOMComponent(tag, omitClose);
48
Constructor.prototype.constructor = Constructor;
49
Constructor.displayName = tag;
50
48
- var ConvenienceConstructor = function(props, children) {
49
- var instance = new Constructor();
50
- instance.construct.apply(instance, arguments);
51
- return instance;
52
- };
53
-
54
- // Expose the constructor on the ConvenienceConstructor and prototype so that
55
- // it can be easily easily accessed on descriptors.
56
- // E.g. <div />.type === div.type
57
- ConvenienceConstructor.type = Constructor;
58
- Constructor.prototype.type = Constructor;
51
+ var ConvenienceConstructor = ReactDescriptor.createFactory(Constructor);
52
60
- Constructor.ConvenienceConstructor = ConvenienceConstructor;
61
- ConvenienceConstructor.componentConstructor = Constructor;
53
return ConvenienceConstructor;
54
}
55
src/browser/ReactTextComponent.js
+8
-22
@@ -22,6 +22,7 @@
22
var DOMPropertyOperations = require('DOMPropertyOperations');
23
var ReactBrowserComponentMixin = require('ReactBrowserComponentMixin');
24
var ReactComponent = require('ReactComponent');
25
+var ReactDescriptor = require('ReactDescriptor');
26
27
var escapeTextForBrowser = require('escapeTextForBrowser');
28
var mixInto = require('mixInto');
@@ -41,18 +42,8 @@ var mixInto = require('mixInto');
42
* @extends ReactComponent
43
* @internal
44
*/
44
-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);
45
+var ReactTextComponent = function(descriptor) {
46
+ this.construct(descriptor);
47
};
48
49
mixInto(ReactTextComponent, ReactComponent.Mixin);
@@ -77,7 +68,7 @@ mixInto(ReactTextComponent, {
68
mountDepth
69
);
70
80
- var escapedText = escapeTextForBrowser(this.props.text);
71
+ var escapedText = escapeTextForBrowser(this.props);
72
73
if (transaction.renderToStaticMarkup) {
74
// Normally we'd wrap this in a `span` for the reasons stated above, but
@@ -102,20 +93,15 @@ mixInto(ReactTextComponent, {
93
*/
94
receiveComponent: function(nextComponent, transaction) {
95
var nextProps = nextComponent.props;
105
- if (nextProps.text !== this.props.text) {
106
- this.props.text = nextProps.text;
96
+ if (nextProps !== this.props) {
97
+ this.props = nextProps;
98
ReactComponent.BackendIDOperations.updateTextContentByID(
99
this._rootNodeID,
109
- nextProps.text
100
+ nextProps
101
);
102
}
103
}
104
105
});
106
116
-// Expose the constructor on itself and the prototype for consistency with other
117
-// descriptors.
118
-ReactTextComponent.type = ReactTextComponent;
119
-ReactTextComponent.prototype.type = ReactTextComponent;
120
-
121
-module.exports = ReactTextComponent;
107
+module.exports = ReactDescriptor.createFactory(ReactTextComponent);
src/browser/server/ReactServerRendering.js
+3
-3
@@ -18,7 +18,7 @@
18
*/
19
"use strict";
20
21
-var ReactComponent = require('ReactComponent');
21
+var ReactDescriptor = require('ReactDescriptor');
22
var ReactInstanceHandles = require('ReactInstanceHandles');
23
var ReactMarkupChecksum = require('ReactMarkupChecksum');
24
var ReactServerRenderingTransaction =
@@ -33,7 +33,7 @@ var invariant = require('invariant');
33
*/
34
function renderComponentToString(component) {
35
invariant(
36
- ReactComponent.isValidComponent(component),
36
+ ReactDescriptor.isValidDescriptor(component),
37
'renderComponentToString(): You must pass a valid ReactComponent.'
38
);
39
@@ -65,7 +65,7 @@ function renderComponentToString(component) {
65
*/
66
function renderComponentToStaticMarkup(component) {
67
invariant(
68
- ReactComponent.isValidComponent(component),
68
+ ReactDescriptor.isValidDescriptor(component),
69
'renderComponentToStaticMarkup(): You must pass a valid ReactComponent.'
70
);
71
src/browser/ui/React.js
+3
-2
@@ -25,6 +25,7 @@ var ReactComponent = require('ReactComponent');
25
var ReactCompositeComponent = require('ReactCompositeComponent');
26
var ReactContext = require('ReactContext');
27
var ReactCurrentOwner = require('ReactCurrentOwner');
28
+var ReactDescriptor = require('ReactDescriptor');
29
var ReactDOM = require('ReactDOM');
30
var ReactDOMComponent = require('ReactDOMComponent');
31
var ReactDefaultInjection = require('ReactDefaultInjection');
@@ -63,8 +64,8 @@ var React = {
64
renderComponentToStaticMarkup:
65
ReactServerRendering.renderComponentToStaticMarkup,
66
unmountComponentAtNode: ReactMount.unmountComponentAtNode,
66
- isValidClass: ReactCompositeComponent.isValidClass,
67
- isValidComponent: ReactComponent.isValidComponent,
67
+ isValidClass: ReactDescriptor.isValidFactory,
68
+ isValidComponent: ReactDescriptor.isValidDescriptor,
69
withContext: ReactContext.withContext,
70
__internals: {
71
Component: ReactComponent,
src/browser/ui/ReactDOMComponent.js
+17
-15
@@ -205,19 +205,22 @@ ReactDOMComponent.Mixin = {
205
return '';
206
},
207
208
- receiveComponent: function(nextComponent, transaction) {
209
- if (nextComponent === this) {
210
- // Since props and context are immutable after the component is
211
- // mounted, we can do a cheap identity compare here to determine
212
- // if this is a superfluous reconcile.
213
-
214
- // TODO: compare the descriptor
208
+ receiveComponent: function(nextDescriptor, transaction) {
209
+ if (nextDescriptor === this._descriptor &&
210
+ nextDescriptor._owner != null) {
211
+ // Since descriptors are immutable after the owner is rendered,
212
+ // we can do a cheap identity compare here to determine if this is a
213
+ // superfluous reconcile. It's possible for state to be mutable but such
214
+ // change should trigger an update of the owner which would recreate
215
+ // the descriptor. We explicitly check for the existance of an owner since
216
+ // it's possible for a descriptor created outside a composite to be
217
+ // deeply mutated and reused.
218
return;
219
}
220
221
ReactComponent.Mixin.receiveComponent.call(
222
this,
220
- nextComponent,
223
+ nextDescriptor,
224
transaction
225
);
226
},
@@ -227,23 +230,22 @@ ReactDOMComponent.Mixin = {
230
* attached to the DOM. Reconciles the root DOM node, then recurses.
231
*
232
* @param {ReactReconcileTransaction} transaction
230
- * @param {object} prevProps
233
+ * @param {ReactDescriptor} prevDescriptor
234
* @internal
235
* @overridable
236
*/
237
updateComponent: ReactPerf.measure(
238
'ReactDOMComponent',
239
'updateComponent',
237
- function(transaction, prevProps, prevOwner) {
238
- assertValidProps(this.props);
240
+ function(transaction, prevDescriptor) {
241
+ assertValidProps(this._descriptor.props);
242
ReactComponent.Mixin.updateComponent.call(
243
this,
244
transaction,
242
- prevProps,
243
- prevOwner
245
+ prevDescriptor
246
);
245
- this._updateDOMProperties(prevProps, transaction);
246
- this._updateDOMChildren(prevProps, transaction);
247
+ this._updateDOMProperties(prevDescriptor.props, transaction);
248
+ this._updateDOMChildren(prevDescriptor.props, transaction);
249
}
250
),
251
src/browser/ui/ReactMount.js
+6
-5
@@ -326,19 +326,20 @@ var ReactMount = {
326
* perform an update on it and only mutate the DOM as necessary to reflect the
327
* latest React component.
328
*
329
- * @param {ReactComponent} nextComponent Component instance to render.
329
+ * @param {ReactDescriptor} nextDescriptor Component descriptor to render.
330
* @param {DOMElement} container DOM element to render into.
331
* @param {?function} callback function triggered on completion
332
* @return {ReactComponent} Component instance rendered in `container`.
333
*/
334
- renderComponent: function(nextComponent, container, callback) {
334
+ renderComponent: function(nextDescriptor, container, callback) {
335
var prevComponent = instancesByReactRootID[getReactRootID(container)];
336
337
if (prevComponent) {
338
- if (shouldUpdateReactComponent(prevComponent, nextComponent)) {
338
+ var prevDescriptor = prevComponent._descriptor;
339
+ if (shouldUpdateReactComponent(prevDescriptor, nextDescriptor)) {
340
return ReactMount._updateRootComponent(
341
prevComponent,
341
- nextComponent,
342
+ nextDescriptor,
343
container,
344
callback
345
);
@@ -354,7 +355,7 @@ var ReactMount = {
355
var shouldReuseMarkup = containerHasReactMarkup && !prevComponent;
356
357
var component = ReactMount._renderNewRootComponent(
357
- nextComponent,
358
+ nextDescriptor,
359
container,
360
shouldReuseMarkup
361
);
src/browser/ui/__tests__/ReactDOMComponent-test.js
+8
-3
@@ -307,8 +307,8 @@ describe('ReactDOMComponent', function() {
307
var ReactDOMComponent = require('ReactDOMComponent');
308
var ReactReconcileTransaction = require('ReactReconcileTransaction');
309
310
- var StubNativeComponent = function(initialProps) {
311
- ReactComponent.Mixin.construct.call(this, initialProps);
310
+ var StubNativeComponent = function(descriptor) {
311
+ ReactComponent.Mixin.construct.call(this, descriptor);
312
};
313
mixInto(StubNativeComponent, ReactComponent.Mixin);
314
mixInto(StubNativeComponent, ReactDOMComponent.Mixin);
@@ -316,7 +316,12 @@ describe('ReactDOMComponent', function() {
316
317
mountComponent = function(props) {
318
var transaction = new ReactReconcileTransaction();
319
- var stubComponent = new StubNativeComponent(props);
319
+ var stubComponent = new StubNativeComponent({
320
+ type: StubNativeComponent,
321
+ props: props,
322
+ _owner: null,
323
+ _context: null
324
+ });
325
return stubComponent.mountComponent('test', transaction, 0);
326
};
327
});
src/browser/ui/__tests__/ReactEventTopLevelCallback-test.js
+16
-5
@@ -121,17 +121,28 @@ describe('ReactEventTopLevelCallback', function() {
121
122
it('should not fire duplicate events for a React DOM tree', function() {
123
var container = document.createElement('div');
124
- var inner = <div>Inner</div>;
125
- var control = <div><div id="outer">{inner}</div></div>;
126
- ReactMount.renderComponent(control, container);
124
+ var Wrapper = React.createClass({
125
+
126
+ getInner: function() {
127
+ return this.refs.inner;
128
+ },
129
+
130
+ render: function() {
131
+ var inner = <div ref="inner">Inner</div>;
132
+ return <div><div id="outer">{inner}</div></div>;
133
+ }
134
+
135
+ });
136
+
137
+ var instance = ReactMount.renderComponent(<Wrapper />, container);
138
139
var callback = ReactEventTopLevelCallback.createTopLevelCallback('test');
140
callback({
130
- target: inner.getDOMNode()
141
+ target: instance.getInner().getDOMNode()
142
});
143
144
var calls = ReactEventEmitter.handleTopLevel.mock.calls;
145
expect(calls.length).toBe(1);
135
- expect(calls[0][EVENT_TARGET_PARAM]).toBe(inner.getDOMNode());
146
+ expect(calls[0][EVENT_TARGET_PARAM]).toBe(instance.getInner().getDOMNode());
147
});
148
});
src/browser/ui/dom/__tests__/Danger-test.js
+10
-3
@@ -21,6 +21,7 @@
21
"use strict";
22
23
var React = require('React');
24
+var instantiateReactComponent = require('instantiateReactComponent');
25
26
describe('Danger', function() {
27
@@ -37,14 +38,18 @@ describe('Danger', function() {
38
});
39
40
it('should render markup', function() {
40
- var markup = (<div />).mountComponent('.rX', transaction, 0);
41
+ var markup = instantiateReactComponent(
42
+ <div />
43
+ ).mountComponent('.rX', transaction, 0);
44
var output = Danger.dangerouslyRenderMarkup([markup])[0];
45
46
expect(output.nodeName).toBe('DIV');
47
});
48
49
it('should render markup with props', function() {
47
- var markup = (<div className="foo" />).mountComponent(
50
+ var markup = instantiateReactComponent(
51
+ <div className="foo" />
52
+ ).mountComponent(
53
'.rX',
54
transaction,
55
0
@@ -56,7 +61,9 @@ describe('Danger', function() {
61
});
62
63
it('should render wrapped markup', function() {
59
- var markup = (<th />).mountComponent('.rX', transaction, 0);
64
+ var markup = instantiateReactComponent(
65
+ <th />
66
+ ).mountComponent('.rX', transaction, 0);
67
var output = Danger.dangerouslyRenderMarkup([markup])[0];
68
69
expect(output.nodeName).toBe('TH');
src/browser/ui/dom/components/createFullPageComponent.js
+1
-1
@@ -38,7 +38,7 @@ var invariant = require('invariant');
38
function createFullPageComponent(componentClass) {
39
var FullPageComponent = ReactCompositeComponent.createClass({
40
displayName: 'ReactFullPageComponent' + (
41
- componentClass.componentConstructor.displayName || ''
41
+ componentClass.type.displayName || ''
42
),
43
44
componentWillUnmount: function() {
src/core/ReactComponent.js
+84
-212
@@ -18,14 +18,13 @@
18
19
"use strict";
20
21
-var ReactCurrentOwner = require('ReactCurrentOwner');
21
+var ReactDescriptor = require('ReactDescriptor');
22
var ReactOwner = require('ReactOwner');
23
var ReactUpdates = require('ReactUpdates');
24
25
var invariant = require('invariant');
26
var keyMirror = require('keyMirror');
27
var merge = require('merge');
28
-var monitorCodeUse = require('monitorCodeUse');
28
29
/**
30
* Every React component is in one of these life cycles.
@@ -42,18 +41,6 @@ var ComponentLifeCycle = keyMirror({
41
UNMOUNTED: null
42
});
43
45
-/**
46
- * Warn if there's no key explicitly set on dynamic arrays of children or
47
- * object keys are not valid. This allows us to keep track of children between
48
- * updates.
49
- */
50
-
51
-var ownerHasExplicitKeyWarning = {};
52
-var ownerHasPropertyWarning = {};
53
-var ownerHasMonitoredObjectMap = {};
54
-
55
-var NUMERIC_PROPERTY_REGEX = /^\d+$/;
56
-
44
var injected = false;
45
46
/**
@@ -76,130 +63,6 @@ var unmountIDFromEnvironment = null;
63
*/
64
var mountImageIntoNode = null;
65
79
-/**
80
- * Warn if the component doesn't have an explicit key assigned to it.
81
- * This component is in an array. The array could grow and shrink or be
82
- * reordered. All children that haven't already been validated are required to
83
- * have a "key" property assigned to it.
84
- *
85
- * @internal
86
- * @param {ReactComponent} component Component that requires a key.
87
- */
88
-function validateExplicitKey(component) {
89
- if (component.__keyValidated__ || component.props.key != null) {
90
- return;
91
- }
92
- component.__keyValidated__ = true;
93
-
94
- // We can't provide friendly warnings for top level components.
95
- if (!ReactCurrentOwner.current) {
96
- return;
97
- }
98
-
99
- // Name of the component whose render method tried to pass children.
100
- var currentName = ReactCurrentOwner.current.constructor.displayName;
101
- if (ownerHasExplicitKeyWarning.hasOwnProperty(currentName)) {
102
- return;
103
- }
104
- ownerHasExplicitKeyWarning[currentName] = true;
105
-
106
- var message = 'Each child in an array should have a unique "key" prop. ' +
107
- 'Check the render method of ' + currentName + '.';
108
-
109
- var childOwnerName = null;
110
- if (!component.isOwnedBy(ReactCurrentOwner.current)) {
111
- // Name of the component that originally created this child.
112
- childOwnerName =
113
- component._owner &&
114
- component._owner.constructor.displayName;
115
-
116
- // Usually the current owner is the offender, but if it accepts
117
- // children as a property, it may be the creator of the child that's
118
- // responsible for assigning it a key.
119
- message += ' It was passed a child from ' + childOwnerName + '.';
120
- }
121
-
122
- message += ' See http://fb.me/react-warning-keys for more information.';
123
- monitorCodeUse('react_key_warning', {
124
- component: currentName,
125
- componentOwner: childOwnerName
126
- });
127
- console.warn(message);
128
-}
129
-
130
-/**
131
- * Warn if the key is being defined as an object property but has an incorrect
132
- * value.
133
- *
134
- * @internal
135
- * @param {string} name Property name of the key.
136
- * @param {ReactComponent} component Component that requires a key.
137
- */
138
-function validatePropertyKey(name) {
139
- if (NUMERIC_PROPERTY_REGEX.test(name)) {
140
- // Name of the component whose render method tried to pass children.
141
- var currentName = ReactCurrentOwner.current.constructor.displayName;
142
- if (ownerHasPropertyWarning.hasOwnProperty(currentName)) {
143
- return;
144
- }
145
- ownerHasPropertyWarning[currentName] = true;
146
-
147
- monitorCodeUse('react_numeric_key_warning');
148
- console.warn(
149
- 'Child objects should have non-numeric keys so ordering is preserved. ' +
150
- 'Check the render method of ' + currentName + '. ' +
151
- 'See http://fb.me/react-warning-keys for more information.'
152
- );
153
- }
154
-}
155
-
156
-/**
157
- * Log that we're using an object map. We're considering deprecating this
158
- * feature and replace it with proper Map and ImmutableMap data structures.
159
- *
160
- * @internal
161
- */
162
-function monitorUseOfObjectMap() {
163
- // Name of the component whose render method tried to pass children.
164
- // We only use this to avoid spewing the logs. We lose additional
165
- // owner stacks but hopefully one level is enough to trace the source.
166
- var currentName = (ReactCurrentOwner.current &&
167
- ReactCurrentOwner.current.constructor.displayName) || '';
168
- if (ownerHasMonitoredObjectMap.hasOwnProperty(currentName)) {
169
- return;
170
- }
171
- ownerHasMonitoredObjectMap[currentName] = true;
172
- monitorCodeUse('react_object_map_children');
173
-}
174
-
175
-/**
176
- * Ensure that every component either is passed in a static location, in an
177
- * array with an explicit keys property defined, or in an object literal
178
- * with valid key property.
179
- *
180
- * @internal
181
- * @param {*} component Statically passed child of any type.
182
- * @return {boolean}
183
- */
184
-function validateChildKeys(component) {
185
- if (Array.isArray(component)) {
186
- for (var i = 0; i < component.length; i++) {
187
- var child = component[i];
188
- if (ReactComponent.isValidComponent(child)) {
189
- validateExplicitKey(child);
190
- }
191
- }
192
- } else if (ReactComponent.isValidComponent(component)) {
193
- // This component was passed in a valid location.
194
- component.__keyValidated__ = true;
195
- } else if (component && typeof component === 'object') {
196
- monitorUseOfObjectMap();
197
- for (var name in component) {
198
- validatePropertyKey(name, component);
199
- }
200
- }
201
-}
202
-
66
/**
67
* Components are the basic units of composition in React.
68
*
@@ -244,26 +107,6 @@ var ReactComponent = {
107
}
108
},
109
247
- /**
248
- * @param {?object} object
249
- * @return {boolean} True if `object` is a valid component.
250
- * @final
251
- */
252
- isValidComponent: function(object) {
253
- if (!object || !object.type || !object.type.prototype) {
254
- return false;
255
- }
256
- // This is the safer way of duck checking the type of instance this is.
257
- // The object can be a generic descriptor but the type property refers to
258
- // the constructor and it's prototype can be used to inspect the type that
259
- // will actually get mounted.
260
- var prototype = object.type.prototype;
261
- return (
262
- typeof prototype.mountComponentIntoNode === 'function' &&
263
- typeof prototype.receiveComponent === 'function'
264
- );
265
- },
266
-
110
/**
111
* @internal
112
*/
@@ -314,9 +157,11 @@ var ReactComponent = {
157
* @public
158
*/
159
setProps: function(partialProps, callback) {
317
- // Merge with `_pendingProps` if it exists, otherwise with existing props.
160
+ // Merge with the pending descriptor if it exists, otherwise with existing
161
+ // descriptor props.
162
+ var descriptor = this._pendingDescriptor || this._descriptor;
163
this.replaceProps(
319
- merge(this._pendingProps || this.props, partialProps),
164
+ merge(descriptor.props, partialProps),
165
callback
166
);
167
},
@@ -342,7 +187,31 @@ var ReactComponent = {
187
'`render` method to pass the correct value as props to the component ' +
188
'where it is created.'
189
);
345
- this._pendingProps = props;
190
+ // This is a deoptimized path. We optimize for always having a descriptor.
191
+ // This creates an extra internal descriptor.
192
+ this._pendingDescriptor = ReactDescriptor.cloneAndReplaceProps(
193
+ this._pendingDescriptor || this._descriptor,
194
+ props
195
+ );
196
+ ReactUpdates.enqueueUpdate(this, callback);
197
+ },
198
+
199
+ /**
200
+ * Schedule a partial update to the props. Only used for internal testing.
201
+ *
202
+ * @param {object} partialProps Subset of the next props.
203
+ * @param {?function} callback Called after props are updated.
204
+ * @final
205
+ * @internal
206
+ */
207
+ _setPropsInternal: function(partialProps, callback) {
208
+ // This is a deoptimized path. We optimize for always having a descriptor.
209
+ // This creates an extra internal descriptor.
210
+ var descriptor = this._pendingDescriptor || this._descriptor;
211
+ this._pendingDescriptor = ReactDescriptor.cloneAndReplaceProps(
212
+ descriptor,
213
+ merge(descriptor.props, partialProps)
214
+ );
215
ReactUpdates.enqueueUpdate(this, callback);
216
},
217
@@ -352,43 +221,30 @@ var ReactComponent = {
221
* Subclasses that override this method should make sure to invoke
222
* `ReactComponent.Mixin.construct.call(this, ...)`.
223
*
355
- * @param {?object} initialProps
356
- * @param {*} children
224
+ * @param {ReactDescriptor} descriptor
225
* @internal
226
*/
359
- construct: function(initialProps, children) {
360
- this.props = initialProps || {};
227
+ construct: function(descriptor) {
228
+ // This is the public exposed props object after it has been processed
229
+ // with default props. The descriptor's props represents the true internal
230
+ // state of the props.
231
+ this.props = descriptor.props;
232
// Record the component responsible for creating this component.
362
- this._owner = ReactCurrentOwner.current;
233
+ // This is accessible through the descriptor but we maintain an extra
234
+ // field for compatibility with devtools and as a way to make an
235
+ // incremental update. TODO: Consider deprecating this field.
236
+ this._owner = descriptor._owner;
237
+
238
// All components start unmounted.
239
this._lifeCycleState = ComponentLifeCycle.UNMOUNTED;
240
366
- this._pendingProps = null;
241
+ // See ReactUpdates.
242
this._pendingCallbacks = null;
243
369
- // Unlike _pendingProps and _pendingCallbacks, we won't use null to
370
- // indicate that nothing is pending because it's possible for a component
371
- // to have a null owner. Instead, an owner change is pending when
372
- // this._owner !== this._pendingOwner.
373
- this._pendingOwner = this._owner;
374
-
375
- // Children can be more than one argument
376
- var childrenLength = arguments.length - 1;
377
- if (childrenLength === 1) {
378
- if (__DEV__) {
379
- validateChildKeys(children);
380
- }
381
- this.props.children = children;
382
- } else if (childrenLength > 1) {
383
- var childArray = Array(childrenLength);
384
- for (var i = 0; i < childrenLength; i++) {
385
- if (__DEV__) {
386
- validateChildKeys(arguments[i + 1]);
387
- }
388
- childArray[i] = arguments[i + 1];
389
- }
390
- this.props.children = childArray;
391
- }
244
+ // We keep the old descriptor and a reference to the pending descriptor
245
+ // to track updates.
246
+ this._descriptor = descriptor;
247
+ this._pendingDescriptor = null;
248
},
249
250
/**
@@ -413,9 +269,10 @@ var ReactComponent = {
269
'single component instance in multiple places.',
270
rootID
271
);
416
- var props = this.props;
272
+ var props = this._descriptor.props;
273
if (props.ref != null) {
418
- ReactOwner.addComponentAsRefTo(this, props.ref, this._owner);
274
+ var owner = this._descriptor._owner;
275
+ ReactOwner.addComponentAsRefTo(this, props.ref, owner);
276
}
277
this._rootNodeID = rootID;
278
this._lifeCycleState = ComponentLifeCycle.MOUNTED;
@@ -458,13 +315,12 @@ var ReactComponent = {
315
* @param {ReactReconcileTransaction} transaction
316
* @internal
317
*/
461
- receiveComponent: function(nextComponent, transaction) {
318
+ receiveComponent: function(nextDescriptor, transaction) {
319
invariant(
320
this.isMounted(),
321
'receiveComponent(...): Can only update a mounted component.'
322
);
466
- this._pendingOwner = nextComponent._owner;
467
- this._pendingProps = nextComponent.props;
323
+ this._pendingDescriptor = nextDescriptor;
324
this._performUpdateIfNecessary(transaction);
325
},
326
@@ -480,44 +336,60 @@ var ReactComponent = {
336
},
337
338
/**
483
- * If `_pendingProps` is set, update the component.
339
+ * If `_pendingDescriptor` is set, update the component.
340
*
341
* @param {ReactReconcileTransaction} transaction
342
* @internal
343
*/
344
_performUpdateIfNecessary: function(transaction) {
489
- if (this._pendingProps == null) {
345
+ if (this._pendingDescriptor == null) {
346
return;
347
}
492
- var prevProps = this.props;
493
- var prevOwner = this._owner;
494
- this.props = this._pendingProps;
495
- this._owner = this._pendingOwner;
496
- this._pendingProps = null;
497
- this.updateComponent(transaction, prevProps, prevOwner);
348
+ var prevDescriptor = this._descriptor;
349
+ var nextDescriptor = this._pendingDescriptor;
350
+ this._descriptor = nextDescriptor;
351
+ this.props = nextDescriptor.props;
352
+ this._owner = nextDescriptor._owner;
353
+ this._pendingDescriptor = null;
354
+ this.updateComponent(transaction, prevDescriptor);
355
},
356
357
/**
358
* Updates the component's currently mounted representation.
359
*
360
* @param {ReactReconcileTransaction} transaction
504
- * @param {object} prevProps
361
+ * @param {object} prevDescriptor
362
* @internal
363
*/
507
- updateComponent: function(transaction, prevProps, prevOwner) {
508
- var props = this.props;
364
+ updateComponent: function(transaction, prevDescriptor) {
365
+ var nextDescriptor = this._descriptor;
366
+
367
// If either the owner or a `ref` has changed, make sure the newest owner
368
// has stored a reference to `this`, and the previous owner (if different)
511
- // has forgotten the reference to `this`.
512
- if (this._owner !== prevOwner || props.ref !== prevProps.ref) {
513
- if (prevProps.ref != null) {
369
+ // has forgotten the reference to `this`. We use the descriptor instead
370
+ // of the public this.props because the post processing cannot determine
371
+ // a ref. The ref conceptually lives on the descriptor.
372
+
373
+ // TODO: Should this even be possible? The owner cannot change because
374
+ // it's forbidden by shouldUpdateReactComponent. The ref can change
375
+ // if you swap the keys of but not the refs. Reconsider where this check
376
+ // is made. It probably belongs where the key checking and
377
+ // instantiateReactComponent is done.
378
+
379
+ if (nextDescriptor._owner !== prevDescriptor._owner ||
380
+ nextDescriptor.props.ref !== prevDescriptor.props.ref) {
381
+ if (prevDescriptor.props.ref != null) {
382
ReactOwner.removeComponentAsRefFrom(
515
- this, prevProps.ref, prevOwner
383
+ this, prevDescriptor.props.ref, prevDescriptor._owner
384
);
385
}
386
// Correct, even if the owner is the same, and only the ref has changed.
519
- if (props.ref != null) {
520
- ReactOwner.addComponentAsRefTo(this, props.ref, this._owner);
387
+ if (nextDescriptor.props.ref != null) {
388
+ ReactOwner.addComponentAsRefTo(
389
+ this,
390
+ nextDescriptor.props.ref,
391
+ nextDescriptor._owner
392
+ );
393
}
394
}
395
},
src/core/ReactCompositeComponent.js
+70
-313
@@ -21,6 +21,7 @@
21
var ReactComponent = require('ReactComponent');
22
var ReactContext = require('ReactContext');
23
var ReactCurrentOwner = require('ReactCurrentOwner');
24
+var ReactDescriptor = require('ReactDescriptor');
25
var ReactErrorUtils = require('ReactErrorUtils');
26
var ReactOwner = require('ReactOwner');
27
var ReactPerf = require('ReactPerf');
@@ -327,7 +328,7 @@ var ReactCompositeComponentInterface = {
328
*/
329
var RESERVED_SPEC_KEYS = {
330
displayName: function(ConvenienceConstructor, displayName) {
330
- ConvenienceConstructor.componentConstructor.displayName = displayName;
331
+ ConvenienceConstructor.type.displayName = displayName;
332
},
333
mixins: function(ConvenienceConstructor, mixins) {
334
if (mixins) {
@@ -337,7 +338,7 @@ var RESERVED_SPEC_KEYS = {
338
}
339
},
340
childContextTypes: function(ConvenienceConstructor, childContextTypes) {
340
- var Constructor = ConvenienceConstructor.componentConstructor;
341
+ var Constructor = ConvenienceConstructor.type;
342
validateTypeDef(
343
Constructor,
344
childContextTypes,
@@ -349,7 +350,7 @@ var RESERVED_SPEC_KEYS = {
350
);
351
},
352
contextTypes: function(ConvenienceConstructor, contextTypes) {
352
- var Constructor = ConvenienceConstructor.componentConstructor;
353
+ var Constructor = ConvenienceConstructor.type;
354
validateTypeDef(
355
Constructor,
356
contextTypes,
@@ -358,7 +359,7 @@ var RESERVED_SPEC_KEYS = {
359
Constructor.contextTypes = merge(Constructor.contextTypes, contextTypes);
360
},
361
propTypes: function(ConvenienceConstructor, propTypes) {
361
- var Constructor = ConvenienceConstructor.componentConstructor;
362
+ var Constructor = ConvenienceConstructor.type;
363
validateTypeDef(
364
Constructor,
365
propTypes,
@@ -437,17 +438,17 @@ function validateLifeCycleOnReplaceState(instance) {
438
*/
439
function mixSpecIntoComponent(ConvenienceConstructor, spec) {
440
invariant(
440
- !isValidClass(spec),
441
+ !ReactDescriptor.isValidFactory(spec),
442
'ReactCompositeComponent: You\'re attempting to ' +
443
'use a component class as a mixin. Instead, just use a regular object.'
444
);
445
invariant(
445
- !ReactComponent.isValidComponent(spec),
446
+ !ReactDescriptor.isValidDescriptor(spec),
447
'ReactCompositeComponent: You\'re attempting to ' +
448
'use a component as a mixin. Instead, just use a regular object.'
449
);
450
450
- var Constructor = ConvenienceConstructor.componentConstructor;
451
+ var Constructor = ConvenienceConstructor.type;
452
var proto = Constructor.prototype;
453
for (var name in spec) {
454
var property = spec[name];
@@ -525,7 +526,7 @@ function mixStaticSpecIntoComponent(ConvenienceConstructor, statics) {
526
result = createChainedFunction(existingProperty, property);
527
}
528
ConvenienceConstructor[name] = result;
528
- ConvenienceConstructor.componentConstructor[name] = result;
529
+ ConvenienceConstructor.type[name] = result;
530
}
531
}
532
@@ -590,203 +591,6 @@ function createChainedFunction(one, two) {
591
};
592
}
593
593
-if (__DEV__) {
594
-
595
- var unmountedPropertyWhitelist = {
596
- constructor: true,
597
- construct: true,
598
- isOwnedBy: true, // should be deprecated but can have code mod (internal)
599
- type: 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
-
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';
648
- var owner = ReactCurrentOwner.current;
649
- var ownerName = (owner && owner.constructor.displayName) || 'Unknown';
650
- var warningKey = key + '|' + name + '|' + ownerName;
651
- if (hasWarnedOnComponentType.hasOwnProperty(warningKey)) {
652
- // We have already warned for this combination. Skip it this time.
653
- return;
654
- }
655
- hasWarnedOnComponentType[warningKey] = true;
656
-
657
- var context = owner ? ' in ' + ownerName + '.' : ' at the top level.';
658
- var staticMethodExample = '<' + name + ' />.type.' + key + '(...)';
659
-
660
- monitorCodeUse('react_descriptor_property_access', { component: name });
661
- console.warn(
662
- 'Invalid access to component property "' + key + '" on ' + name +
663
- context + ' See http://fb.me/react-warning-descriptors .' +
664
- ' Use a static method instead: ' + staticMethodExample
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
-
695
- configurable: false,
696
- enumerable: true,
697
-
698
- get: function() {
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
- }
715
- return realValue;
716
- },
717
-
718
- set: function(value) {
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
- });
729
- };
730
-
731
- /**
732
- * Creates a membrane prototype which wraps the original prototype. If any
733
- * property is accessed in an unmounted state, a warning is issued.
734
- *
735
- * @param {object} prototype Original prototype.
736
- * @return {object} The membrane prototype.
737
- * @private
738
- */
739
- var createMountWarningMembrane = function(prototype) {
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
-
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
-
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
782
- // the membrane creation fails, we'll bail out and just use the plain
783
- // constructor without warnings.
784
- return constructor;
785
- }
786
- };
787
-
788
-}
789
-
594
/**
595
* `ReactCompositeComponent` maintains an auxiliary life cycle state in
596
* `this._compositeLifeCycleState` (which can be null).
@@ -844,12 +648,11 @@ var ReactCompositeComponentMixin = {
648
/**
649
* Base constructor for all composite component.
650
*
847
- * @param {?object} initialProps
848
- * @param {*} children
651
+ * @param {ReactDescriptor} descriptor
652
* @final
653
* @internal
654
*/
852
- construct: function(initialProps, children) {
655
+ construct: function(descriptor) {
656
// Children can be either an array or more than one argument
657
ReactComponent.Mixin.construct.apply(this, arguments);
658
ReactOwner.Mixin.construct.apply(this, arguments);
@@ -857,14 +660,9 @@ var ReactCompositeComponentMixin = {
660
this.state = null;
661
this._pendingState = null;
662
663
+ // This is the public post-processed context. The real context and pending
664
+ // context lives on the descriptor.
665
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;
666
667
this._compositeLifeCycleState = null;
668
},
@@ -913,7 +711,7 @@ var ReactCompositeComponentMixin = {
711
);
712
this._compositeLifeCycleState = CompositeLifeCycle.MOUNTING;
713
916
- this.context = this._processContext(this._currentContext);
714
+ this.context = this._processContext(this._descriptor._context);
715
this._defaultProps = this.getDefaultProps ? this.getDefaultProps() : null;
716
this.props = this._processProps(this.props);
717
@@ -1156,28 +954,27 @@ var ReactCompositeComponentMixin = {
954
},
955
956
/**
1159
- * If any of `_pendingProps`, `_pendingState`, or `_pendingForceUpdate` is
1160
- * set, update the component.
957
+ * If any of `_pendingDescriptor`, `_pendingState`, or `_pendingForceUpdate`
958
+ * is set, update the component.
959
*
960
* @param {ReactReconcileTransaction} transaction
961
* @internal
962
*/
963
_performUpdateIfNecessary: function(transaction) {
1166
- if (this._pendingProps == null &&
964
+ if (this._pendingDescriptor == null &&
965
this._pendingState == null &&
1168
- this._pendingContext == null &&
966
!this._pendingForceUpdate) {
967
return;
968
}
969
1173
- var nextFullContext = this._pendingContext || this._currentContext;
1174
- var nextContext = this._processContext(nextFullContext);
1175
- this._pendingContext = null;
1176
-
970
+ var nextContext = this.context;
971
var nextProps = this.props;
1178
- if (this._pendingProps != null) {
1179
- nextProps = this._processProps(this._pendingProps);
1180
- this._pendingProps = null;
972
+ var nextDescriptor = this._descriptor;
973
+ if (this._pendingDescriptor != null) {
974
+ nextDescriptor = this._pendingDescriptor;
975
+ nextContext = this._processContext(nextDescriptor._context);
976
+ nextProps = this._processProps(nextDescriptor.props);
977
+ this._pendingDescriptor = null;
978
979
this._compositeLifeCycleState = CompositeLifeCycle.RECEIVING_PROPS;
980
if (this.componentWillReceiveProps) {
@@ -1187,12 +984,6 @@ var ReactCompositeComponentMixin = {
984
985
this._compositeLifeCycleState = CompositeLifeCycle.RECEIVING_STATE;
986
1190
- // Unlike props, state, and context, we specifically don't want to set
1191
- // _pendingOwner to null here because it's possible for a component to have
1192
- // a null owner, so we instead make `this._owner === this._pendingOwner`
1193
- // mean that there's no owner change pending.
1194
- var nextOwner = this._pendingOwner;
1195
-
987
var nextState = this._pendingState || this.state;
988
this._pendingState = null;
989
@@ -1203,21 +994,23 @@ var ReactCompositeComponentMixin = {
994
this._pendingForceUpdate = false;
995
// Will set `this.props`, `this.state` and `this.context`.
996
this._performComponentUpdate(
997
+ nextDescriptor,
998
nextProps,
1207
- nextOwner,
999
nextState,
1209
- nextFullContext,
1000
nextContext,
1001
transaction
1002
);
1003
} else {
1004
// If it's determined that a component should not update, we still want
1005
// to set props and state.
1006
+ this._descriptor = nextDescriptor;
1007
this.props = nextProps;
1217
- this._owner = nextOwner;
1008
this.state = nextState;
1219
- this._currentContext = nextFullContext;
1009
this.context = nextContext;
1010
+
1011
+ // Owner cannot change because shouldUpdateReactComponent doesn't allow
1012
+ // it. TODO: Remove this._owner completely.
1013
+ this._owner = nextDescriptor._owner;
1014
}
1015
} finally {
1016
this._compositeLifeCycleState = null;
@@ -1228,24 +1021,22 @@ var ReactCompositeComponentMixin = {
1021
* Merges new props and state, notifies delegate methods of update and
1022
* performs update.
1023
*
1231
- * @param {object} nextProps Next object to set as properties.
1232
- * @param {?ReactComponent} nextOwner Next component to set as owner
1024
+ * @param {ReactDescriptor} nextDescriptor Next descriptor
1025
+ * @param {object} nextProps Next public object to set as properties.
1026
* @param {?object} nextState Next object to set as state.
1234
- * @param {?object} nextFullContext Next object to set as _currentContext.
1235
- * @param {?object} nextContext Next object to set as context.
1027
+ * @param {?object} nextContext Next public object to set as context.
1028
* @param {ReactReconcileTransaction} transaction
1029
* @private
1030
*/
1031
_performComponentUpdate: function(
1032
+ nextDescriptor,
1033
nextProps,
1241
- nextOwner,
1034
nextState,
1243
- nextFullContext,
1035
nextContext,
1036
transaction
1037
) {
1038
+ var prevDescriptor = this._descriptor;
1039
var prevProps = this.props;
1248
- var prevOwner = this._owner;
1040
var prevState = this.state;
1041
var prevContext = this.context;
1042
@@ -1253,18 +1044,18 @@ var ReactCompositeComponentMixin = {
1044
this.componentWillUpdate(nextProps, nextState, nextContext);
1045
}
1046
1047
+ this._descriptor = nextDescriptor;
1048
this.props = nextProps;
1257
- this._owner = nextOwner;
1049
this.state = nextState;
1259
- this._currentContext = nextFullContext;
1050
this.context = nextContext;
1051
1052
+ // Owner cannot change because shouldUpdateReactComponent doesn't allow
1053
+ // it. TODO: Remove this._owner completely.
1054
+ this._owner = nextDescriptor._owner;
1055
+
1056
this.updateComponent(
1057
transaction,
1264
- prevProps,
1265
- prevOwner,
1266
- prevState,
1267
- prevContext
1058
+ prevDescriptor
1059
);
1060
1061
if (this.componentDidUpdate) {
@@ -1275,21 +1066,22 @@ var ReactCompositeComponentMixin = {
1066
}
1067
},
1068
1278
- receiveComponent: function(nextComponent, transaction) {
1279
- if (nextComponent === this._descriptor) {
1280
- // Since props and context are immutable after the component is
1281
- // mounted, we can do a cheap identity compare here to determine
1282
- // if this is a superfluous reconcile.
1069
+ receiveComponent: function(nextDescriptor, transaction) {
1070
+ if (nextDescriptor === this._descriptor &&
1071
+ nextDescriptor._owner != null) {
1072
+ // Since descriptors are immutable after the owner is rendered,
1073
+ // we can do a cheap identity compare here to determine if this is a
1074
+ // superfluous reconcile. It's possible for state to be mutable but such
1075
+ // change should trigger an update of the owner which would recreate
1076
+ // the descriptor. We explicitly check for the existance of an owner since
1077
+ // it's possible for a descriptor created outside a composite to be
1078
+ // deeply mutated and reused.
1079
return;
1080
}
1081
1286
- // Update the descriptor that was last used by this component instance
1287
- this._descriptor = nextComponent;
1288
-
1289
- this._pendingContext = nextComponent._currentContext;
1082
ReactComponent.Mixin.receiveComponent.call(
1083
this,
1292
- nextComponent,
1084
+ nextDescriptor,
1085
transaction
1086
);
1087
},
@@ -1301,35 +1093,31 @@ var ReactCompositeComponentMixin = {
1093
* Sophisticated clients may wish to override this.
1094
*
1095
* @param {ReactReconcileTransaction} transaction
1304
- * @param {object} prevProps
1305
- * @param {?ReactComponent} prevOwner
1306
- * @param {?object} prevState
1307
- * @param {?object} prevContext
1096
+ * @param {ReactDescriptor} prevDescriptor
1097
* @internal
1098
* @overridable
1099
*/
1100
updateComponent: ReactPerf.measure(
1101
'ReactCompositeComponent',
1102
'updateComponent',
1314
- function(transaction, prevProps, prevOwner, prevState, prevContext) {
1103
+ function(transaction, prevParentDescriptor) {
1104
ReactComponent.Mixin.updateComponent.call(
1105
this,
1106
transaction,
1318
- prevProps,
1319
- prevOwner
1107
+ prevParentDescriptor
1108
);
1109
1322
-
1110
var prevComponentInstance = this._renderedComponent;
1324
- var nextComponent = this._renderValidatedComponent();
1325
- if (shouldUpdateReactComponent(prevComponentInstance, nextComponent)) {
1326
- prevComponentInstance.receiveComponent(nextComponent, transaction);
1111
+ var prevDescriptor = prevComponentInstance._descriptor;
1112
+ var nextDescriptor = this._renderValidatedComponent();
1113
+ if (shouldUpdateReactComponent(prevDescriptor, nextDescriptor)) {
1114
+ prevComponentInstance.receiveComponent(nextDescriptor, transaction);
1115
} else {
1116
// These two IDs are actually the same! But nothing should rely on that.
1117
var thisID = this._rootNodeID;
1118
var prevComponentID = prevComponentInstance._rootNodeID;
1119
prevComponentInstance.unmountComponent();
1332
- this._renderedComponent = instantiateReactComponent(nextComponent);
1120
+ this._renderedComponent = instantiateReactComponent(nextDescriptor);
1121
var nextMarkup = this._renderedComponent.mountComponent(
1122
thisID,
1123
transaction,
@@ -1384,7 +1172,9 @@ var ReactCompositeComponentMixin = {
1172
function() {
1173
var renderedComponent;
1174
var previousContext = ReactContext.current;
1387
- ReactContext.current = this._processChildContext(this._currentContext);
1175
+ ReactContext.current = this._processChildContext(
1176
+ this._descriptor._context
1177
+ );
1178
ReactCurrentOwner.current = this;
1179
try {
1180
renderedComponent = this.render();
@@ -1393,7 +1183,7 @@ var ReactCompositeComponentMixin = {
1183
ReactCurrentOwner.current = null;
1184
}
1185
invariant(
1396
- ReactComponent.isValidComponent(renderedComponent),
1186
+ ReactDescriptor.isValidDescriptor(renderedComponent),
1187
'%s.render(): A valid ReactComponent must be returned. You may have ' +
1188
'returned null, undefined, an array, or some other invalid object.',
1189
this.constructor.displayName || 'ReactCompositeComponent'
@@ -1471,18 +1261,6 @@ mixInto(ReactCompositeComponentBase, ReactOwner.Mixin);
1261
mixInto(ReactCompositeComponentBase, ReactPropTransferer.Mixin);
1262
mixInto(ReactCompositeComponentBase, ReactCompositeComponentMixin);
1263
1474
-/**
1475
- * Checks if a value is a valid component constructor.
1476
- *
1477
- * @param {*}
1478
- * @return {boolean}
1479
- * @public
1480
- */
1481
-function isValidClass(componentClass) {
1482
- return componentClass instanceof Function &&
1483
- 'componentConstructor' in componentClass &&
1484
- componentClass.componentConstructor instanceof Function;
1485
-}
1264
/**
1265
* Module for creating composite components.
1266
*
@@ -1505,20 +1283,16 @@ var ReactCompositeComponent = {
1283
* @public
1284
*/
1285
createClass: function(spec) {
1508
- var Constructor = function() {};
1286
+ var Constructor = function(props, owner) {
1287
+ this.construct(props, owner);
1288
+ };
1289
Constructor.prototype = new ReactCompositeComponentBase();
1290
Constructor.prototype.constructor = Constructor;
1291
1512
- var DescriptorConstructor = Constructor;
1292
+ var ConvenienceConstructor = ReactDescriptor.createFactory(Constructor);
1293
1514
- var ConvenienceConstructor = function(props, children) {
1515
- var descriptor = new DescriptorConstructor();
1516
- descriptor.construct.apply(descriptor, arguments);
1517
- return descriptor;
1518
- };
1519
- ConvenienceConstructor.componentConstructor = Constructor;
1520
- Constructor.ConvenienceConstructor = ConvenienceConstructor;
1521
- ConvenienceConstructor.originalSpec = spec;
1294
+ // TODO: Move statics off of the convenience constructor. That way the
1295
+ // factory can be created independently from the main class.
1296
1297
injectedMixins.forEach(
1298
mixSpecIntoComponent.bind(null, ConvenienceConstructor)
@@ -1546,14 +1320,6 @@ var ReactCompositeComponent = {
1320
}
1321
}
1322
1549
- // Expose the convience constructor on the prototype so that it can be
1550
- // easily accessed on descriptors. E.g. <Foo />.type === Foo.type and for
1551
- // static methods like <Foo />.type.staticMethod();
1552
- // This should not be named constructor since this may not be the function
1553
- // that created the descriptor, and it may not even be a constructor.
1554
- ConvenienceConstructor.type = Constructor;
1555
- Constructor.prototype.type = Constructor;
1556
-
1323
// Reduce time spent doing lookups by setting these on the prototype.
1324
for (var methodName in ReactCompositeComponentInterface) {
1325
if (!Constructor.prototype[methodName]) {
@@ -1561,18 +1327,9 @@ var ReactCompositeComponent = {
1327
}
1328
}
1329
1564
- if (__DEV__) {
1565
- // In DEV the convenience constructor generates a proxy to another
1566
- // instance around it to warn about access to properties on the
1567
- // descriptor.
1568
- DescriptorConstructor = createDescriptorProxy(Constructor);
1569
- }
1570
-
1330
return ConvenienceConstructor;
1331
},
1332
1574
- isValidClass: isValidClass,
1575
-
1333
injection: {
1334
injectMixin: function(mixin) {
1335
injectedMixins.push(mixin);
src/core/ReactDescriptor.js
new
+350
@@ -0,0 +1,350 @@
1
+/**
2
+ * Copyright 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 ReactDescriptor
17
+ */
18
+
19
+"use strict";
20
+
21
+var ReactContext = require('ReactContext');
22
+var ReactCurrentOwner = require('ReactCurrentOwner');
23
+
24
+var monitorCodeUse = require('monitorCodeUse');
25
+var warning = require('warning');
26
+
27
+/**
28
+ * Warn if there's no key explicitly set on dynamic arrays of children or
29
+ * object keys are not valid. This allows us to keep track of children between
30
+ * updates.
31
+ */
32
+var ownerHasExplicitKeyWarning = {};
33
+var ownerHasPropertyWarning = {};
34
+var ownerHasMonitoredObjectMap = {};
35
+
36
+var NUMERIC_PROPERTY_REGEX = /^\d+$/;
37
+
38
+/**
39
+ * Warn if the component doesn't have an explicit key assigned to it.
40
+ * This component is in an array. The array could grow and shrink or be
41
+ * reordered. All children that haven't already been validated are required to
42
+ * have a "key" property assigned to it.
43
+ *
44
+ * @internal
45
+ * @param {ReactComponent} component Component that requires a key.
46
+ */
47
+function validateExplicitKey(component) {
48
+ if (component._store.validated || component.props.key != null) {
49
+ return;
50
+ }
51
+ component._store.validated = true;
52
+
53
+ // We can't provide friendly warnings for top level components.
54
+ if (!ReactCurrentOwner.current) {
55
+ return;
56
+ }
57
+
58
+ // Name of the component whose render method tried to pass children.
59
+ var currentName = ReactCurrentOwner.current.constructor.displayName;
60
+ if (ownerHasExplicitKeyWarning.hasOwnProperty(currentName)) {
61
+ return;
62
+ }
63
+ ownerHasExplicitKeyWarning[currentName] = true;
64
+
65
+ var message = 'Each child in an array should have a unique "key" prop. ' +
66
+ 'Check the render method of ' + currentName + '.';
67
+
68
+ var childOwnerName = null;
69
+ if (component._owner !== ReactCurrentOwner.current) {
70
+ // Name of the component that originally created this child.
71
+ childOwnerName =
72
+ component._owner &&
73
+ component._owner.constructor.displayName;
74
+
75
+ // Usually the current owner is the offender, but if it accepts
76
+ // children as a property, it may be the creator of the child that's
77
+ // responsible for assigning it a key.
78
+ message += ' It was passed a child from ' + childOwnerName + '.';
79
+ }
80
+
81
+ message += ' See http://fb.me/react-warning-keys for more information.';
82
+ monitorCodeUse('react_key_warning', {
83
+ component: currentName,
84
+ componentOwner: childOwnerName
85
+ });
86
+ console.warn(message);
87
+}
88
+
89
+/**
90
+ * Warn if the key is being defined as an object property but has an incorrect
91
+ * value.
92
+ *
93
+ * @internal
94
+ * @param {string} name Property name of the key.
95
+ * @param {ReactComponent} component Component that requires a key.
96
+ */
97
+function validatePropertyKey(name) {
98
+ if (NUMERIC_PROPERTY_REGEX.test(name)) {
99
+ // Name of the component whose render method tried to pass children.
100
+ var currentName = ReactCurrentOwner.current.constructor.displayName;
101
+ if (ownerHasPropertyWarning.hasOwnProperty(currentName)) {
102
+ return;
103
+ }
104
+ ownerHasPropertyWarning[currentName] = true;
105
+
106
+ monitorCodeUse('react_numeric_key_warning');
107
+ console.warn(
108
+ 'Child objects should have non-numeric keys so ordering is preserved. ' +
109
+ 'Check the render method of ' + currentName + '. ' +
110
+ 'See http://fb.me/react-warning-keys for more information.'
111
+ );
112
+ }
113
+}
114
+
115
+/**
116
+ * Log that we're using an object map. We're considering deprecating this
117
+ * feature and replace it with proper Map and ImmutableMap data structures.
118
+ *
119
+ * @internal
120
+ */
121
+function monitorUseOfObjectMap() {
122
+ // Name of the component whose render method tried to pass children.
123
+ // We only use this to avoid spewing the logs. We lose additional
124
+ // owner stacks but hopefully one level is enough to trace the source.
125
+ var currentName = (ReactCurrentOwner.current &&
126
+ ReactCurrentOwner.current.constructor.displayName) || '';
127
+ if (ownerHasMonitoredObjectMap.hasOwnProperty(currentName)) {
128
+ return;
129
+ }
130
+ ownerHasMonitoredObjectMap[currentName] = true;
131
+ monitorCodeUse('react_object_map_children');
132
+}
133
+
134
+/**
135
+ * Ensure that every component either is passed in a static location, in an
136
+ * array with an explicit keys property defined, or in an object literal
137
+ * with valid key property.
138
+ *
139
+ * @internal
140
+ * @param {*} component Statically passed child of any type.
141
+ * @return {boolean}
142
+ */
143
+function validateChildKeys(component) {
144
+ if (Array.isArray(component)) {
145
+ for (var i = 0; i < component.length; i++) {
146
+ var child = component[i];
147
+ if (ReactDescriptor.isValidDescriptor(child)) {
148
+ validateExplicitKey(child);
149
+ }
150
+ }
151
+ } else if (ReactDescriptor.isValidDescriptor(component)) {
152
+ // This component was passed in a valid location.
153
+ component._store.validated = true;
154
+ } else if (component && typeof component === 'object') {
155
+ monitorUseOfObjectMap();
156
+ for (var name in component) {
157
+ validatePropertyKey(name, component);
158
+ }
159
+ }
160
+}
161
+
162
+/**
163
+ * Warn for mutations.
164
+ *
165
+ * @internal
166
+ * @param {object} object
167
+ * @param {string} key
168
+ */
169
+function defineWarningProperty(object, key) {
170
+ Object.defineProperty(object, key, {
171
+
172
+ configurable: false,
173
+ enumerable: true,
174
+
175
+ get: function() {
176
+ if (!this._store) {
177
+ return null;
178
+ }
179
+ return this._store[key];
180
+ },
181
+
182
+ set: function(value) {
183
+ warning(
184
+ false,
185
+ 'Don\'t set the ' + key + ' property of the component. ' +
186
+ 'Mutate the existing props object instead.'
187
+ );
188
+ this._store[key] = value;
189
+ }
190
+
191
+ });
192
+}
193
+
194
+/**
195
+ * This is updated to true if the membrane is successfully created.
196
+ */
197
+var useMutationMembrane = false;
198
+
199
+/**
200
+ * Warn for mutations.
201
+ *
202
+ * @internal
203
+ * @param {object} descriptor
204
+ */
205
+function defineMutationMembrane(prototype) {
206
+ try {
207
+ var pseudoFrozenProperties = {
208
+ props: true
209
+ };
210
+ for (var key in pseudoFrozenProperties) {
211
+ defineWarningProperty(prototype, key);
212
+ }
213
+ useMutationMembrane = true;
214
+ } catch (x) {
215
+ // IE will fail on defineProperty
216
+ }
217
+}
218
+
219
+/**
220
+ * Base constructor for all React descriptors. This is only used to make this
221
+ * work with a dynamic instanceof check. Nothing should live on this prototype.
222
+ *
223
+ * @param {*} type
224
+ * @internal
225
+ */
226
+var ReactDescriptor = function() {};
227
+
228
+if (__DEV__) {
229
+ defineMutationMembrane(ReactDescriptor.prototype);
230
+}
231
+
232
+ReactDescriptor.createFactory = function(type) {
233
+
234
+ var descriptorPrototype = Object.create(ReactDescriptor.prototype);
235
+
236
+ var factory = function(props, children) {
237
+ if (props == null) {
238
+ props = {};
239
+ }
240
+
241
+ // Children can be more than one argument
242
+ var childrenLength = arguments.length - 1;
243
+ if (childrenLength === 1) {
244
+ if (__DEV__) {
245
+ validateChildKeys(children);
246
+ }
247
+ props.children = children;
248
+ } else if (childrenLength > 1) {
249
+ var childArray = Array(childrenLength);
250
+ for (var i = 0; i < childrenLength; i++) {
251
+ if (__DEV__) {
252
+ validateChildKeys(arguments[i + 1]);
253
+ }
254
+ childArray[i] = arguments[i + 1];
255
+ }
256
+ props.children = childArray;
257
+ }
258
+
259
+ var descriptor = Object.create(descriptorPrototype);
260
+
261
+ // Record the component responsible for creating this descriptor.
262
+ descriptor._owner = ReactCurrentOwner.current;
263
+
264
+ // TODO: Deprecate withContext, and then the context becomes accessible
265
+ // through the owner.
266
+ descriptor._context = ReactContext.current;
267
+
268
+ if (__DEV__) {
269
+ // The validation flag and props are currently mutative. We put them on
270
+ // an external backing store so that we can freeze the whole object.
271
+ descriptor._store = { validated: false, props: props };
272
+
273
+ // We're not allowed to set props directly on the object so we early
274
+ // return and rely on the prototype membrane toward to the backing store.
275
+ if (useMutationMembrane) {
276
+ Object.freeze(descriptor);
277
+ return descriptor;
278
+ }
279
+ }
280
+
281
+ descriptor.props = props;
282
+ return descriptor;
283
+
284
+ };
285
+
286
+ // Currently we expose the prototype of the descriptor so that
287
+ // <Foo /> instanceof Foo works. This is controversial pattern.
288
+ factory.prototype = descriptorPrototype;
289
+
290
+ // Expose the type on the factory and the prototype so that it can be
291
+ // easily accessed on descriptors. E.g. <Foo />.type === Foo.type and for
292
+ // static methods like <Foo />.type.staticMethod();
293
+ // This should not be named constructor since this may not be the function
294
+ // that created the descriptor, and it may not even be a constructor.
295
+ factory.type = type;
296
+ descriptorPrototype.type = type;
297
+
298
+ // Expose a unique constructor on the prototype is that this works with type
299
+ // systems that compare constructor properties: <Foo />.constructor === Foo
300
+ // This may be controversial since it requires a known factory function.
301
+ descriptorPrototype.constructor = factory;
302
+
303
+ return factory;
304
+
305
+};
306
+
307
+ReactDescriptor.cloneAndReplaceProps = function(oldDescriptor, newProps) {
308
+ var newDescriptor = Object.create(oldDescriptor.constructor.prototype);
309
+ // It's important that this property order matches the hidden class of the
310
+ // original descriptor to maintain perf.
311
+ newDescriptor._owner = oldDescriptor._owner;
312
+ newDescriptor._context = oldDescriptor._context;
313
+
314
+ if (__DEV__) {
315
+ newDescriptor._store = {
316
+ validated: oldDescriptor._store.validated,
317
+ props: newProps
318
+ };
319
+ if (useMutationMembrane) {
320
+ Object.freeze(newDescriptor);
321
+ return newDescriptor;
322
+ }
323
+ }
324
+
325
+ newDescriptor.props = newProps;
326
+ return newDescriptor;
327
+};
328
+
329
+/**
330
+ * Checks if a value is a valid descriptor constructor.
331
+ *
332
+ * @param {*}
333
+ * @return {boolean}
334
+ * @public
335
+ */
336
+ReactDescriptor.isValidFactory = function(factory) {
337
+ return typeof factory === 'function' &&
338
+ factory.prototype instanceof ReactDescriptor;
339
+};
340
+
341
+/**
342
+ * @param {?object} object
343
+ * @return {boolean} True if `object` is a valid component.
344
+ * @final
345
+ */
346
+ReactDescriptor.isValidDescriptor = function(object) {
347
+ return object instanceof ReactDescriptor;
348
+};
349
+
350
+module.exports = ReactDescriptor;
src/core/ReactMultiChild.js
+9
-11
@@ -286,11 +286,12 @@ var ReactMultiChild = {
286
continue;
287
}
288
var prevChild = prevChildren && prevChildren[name];
289
- var nextChild = nextChildren[name];
290
- if (shouldUpdateReactComponent(prevChild, nextChild)) {
289
+ var prevDescriptor = prevChild && prevChild._descriptor;
290
+ var nextDescriptor = nextChildren[name];
291
+ if (shouldUpdateReactComponent(prevDescriptor, nextDescriptor)) {
292
this.moveChild(prevChild, nextIndex, lastIndex);
293
lastIndex = Math.max(prevChild._mountIndex, lastIndex);
293
- prevChild.receiveComponent(nextChild, transaction);
294
+ prevChild.receiveComponent(nextDescriptor, transaction);
295
prevChild._mountIndex = nextIndex;
296
} else {
297
if (prevChild) {
@@ -299,7 +300,7 @@ var ReactMultiChild = {
300
this._unmountChildByName(prevChild, name);
301
}
302
// The child must be instantiated before it's mounted.
302
- var nextChildInstance = instantiateReactComponent(nextChild);
303
+ var nextChildInstance = instantiateReactComponent(nextDescriptor);
304
this._mountChildByNameAtIndex(
305
nextChildInstance, name, nextIndex, transaction
306
);
@@ -416,13 +417,10 @@ var ReactMultiChild = {
417
* @private
418
*/
419
_unmountChildByName: function(child, name) {
419
- // TODO: When is this not true?
420
- if (ReactComponent.isValidComponent(child)) {
421
- this.removeChild(child);
422
- child._mountIndex = null;
423
- child.unmountComponent();
424
- delete this._renderedChildren[name];
425
- }
420
+ this.removeChild(child);
421
+ child._mountIndex = null;
422
+ child.unmountComponent();
423
+ delete this._renderedChildren[name];
424
}
425
426
}
src/core/ReactPropTransferer.js
+35
-27
@@ -68,6 +68,31 @@ var TransferStrategies = {
68
style: createTransferStrategy(merge)
69
};
70
71
+/**
72
+ * Mutates the first argument by transferring the properties from the second
73
+ * argument.
74
+ *
75
+ * @param {object} props
76
+ * @param {object} newProps
77
+ * @return {object}
78
+ */
79
+function transferInto(props, newProps) {
80
+ for (var thisKey in newProps) {
81
+ if (!newProps.hasOwnProperty(thisKey)) {
82
+ continue;
83
+ }
84
+
85
+ var transferStrategy = TransferStrategies[thisKey];
86
+
87
+ if (transferStrategy && TransferStrategies.hasOwnProperty(thisKey)) {
88
+ transferStrategy(props, thisKey, newProps[thisKey]);
89
+ } else if (!props.hasOwnProperty(thisKey)) {
90
+ props[thisKey] = newProps[thisKey];
91
+ }
92
+ }
93
+ return props;
94
+}
95
+
96
/**
97
* ReactPropTransferer are capable of transferring props to another component
98
* using a `transferPropsTo` method.
@@ -86,23 +111,7 @@ var ReactPropTransferer = {
111
* @return {object} a new object containing both sets of props merged.
112
*/
113
mergeProps: function(oldProps, newProps) {
89
- var props = merge(oldProps);
90
-
91
- for (var thisKey in newProps) {
92
- if (!newProps.hasOwnProperty(thisKey)) {
93
- continue;
94
- }
95
-
96
- var transferStrategy = TransferStrategies[thisKey];
97
-
98
- if (transferStrategy && TransferStrategies.hasOwnProperty(thisKey)) {
99
- transferStrategy(props, thisKey, newProps[thisKey]);
100
- } else if (!props.hasOwnProperty(thisKey)) {
101
- props[thisKey] = newProps[thisKey];
102
- }
103
- }
104
-
105
- return props;
114
+ return transferInto(merge(oldProps), newProps);
115
},
116
117
/**
@@ -118,27 +127,26 @@ var ReactPropTransferer = {
127
*
128
* This is usually used to pass down props to a returned root component.
129
*
121
- * @param {ReactComponent} component Component receiving the properties.
122
- * @return {ReactComponent} The supplied `component`.
130
+ * @param {ReactDescriptor} descriptor Component receiving the properties.
131
+ * @return {ReactDescriptor} The supplied `component`.
132
* @final
133
* @protected
134
*/
126
- transferPropsTo: function(component) {
135
+ transferPropsTo: function(descriptor) {
136
invariant(
128
- component._owner === this,
137
+ descriptor._owner === this,
138
'%s: You can\'t call transferPropsTo() on a component that you ' +
139
'don\'t own, %s. This usually means you are calling ' +
140
'transferPropsTo() on a component passed in as props or children.',
141
this.constructor.displayName,
133
- component.constructor.displayName
142
+ descriptor.type.displayName
143
);
144
136
- component.props = ReactPropTransferer.mergeProps(
137
- component.props,
138
- this.props
139
- );
145
+ // Because descriptors are immutable we have to merge into the existing
146
+ // props object rather than clone it.
147
+ transferInto(descriptor.props, this.props);
148
141
- return component;
149
+ return descriptor;
150
}
151
152
}
src/core/ReactPropTypes.js
+3
-3
@@ -18,7 +18,7 @@
18
19
"use strict";
20
21
-var ReactComponent = require('ReactComponent');
21
+var ReactDescriptor = require('ReactDescriptor');
22
var ReactPropTypeLocationNames = require('ReactPropTypeLocationNames');
23
24
var warning = require('warning');
@@ -106,7 +106,7 @@ function isRenderable(propValue) {
106
if (Array.isArray(propValue)) {
107
return propValue.every(isRenderable);
108
}
109
- if (ReactComponent.isValidComponent(propValue)) {
109
+ if (ReactDescriptor.isValidDescriptor(propValue)) {
110
return true;
111
}
112
for (var k in propValue) {
@@ -279,7 +279,7 @@ function createComponentTypeChecker() {
279
function validateComponentType(
280
shouldWarn, propValue, propName, componentName, location
281
) {
282
- var isValid = ReactComponent.isValidComponent(propValue);
282
+ var isValid = ReactDescriptor.isValidDescriptor(propValue);
283
if (shouldWarn) {
284
warning(
285
isValid,
src/core/__tests__/ReactComponent-test.js
+20
-7
@@ -65,7 +65,8 @@ describe('ReactComponent', function() {
65
}
66
});
67
68
- var instance = <Component child={<span />} />;
68
+ var childInstance = ReactTestUtils.renderIntoDocument(<span />);
69
+ var instance = <Component child={childInstance} />;
70
71
expect(function() {
72
instance = ReactTestUtils.renderIntoDocument(instance);
@@ -76,21 +77,33 @@ describe('ReactComponent', function() {
77
});
78
79
it('should support refs on owned components', function() {
79
- var inner, outer;
80
+ var innerObj = {}, outerObj = {};
81
+
82
+ var Wrapper = React.createClass({
83
+
84
+ getObject: function() {
85
+ return this.props.object;
86
+ },
87
+
88
+ render: function() {
89
+ return <div>{this.props.children}</div>;
90
+ }
91
+
92
+ });
93
94
var Component = React.createClass({
95
render: function() {
83
- inner = <div ref="inner" />;
84
- outer = <div ref="outer">{inner}</div>;
96
+ var inner = <Wrapper object={innerObj} ref="inner" />;
97
+ var outer = <Wrapper object={outerObj} ref="outer">{inner}</Wrapper>;
98
return outer;
99
},
100
componentDidMount: function() {
88
- expect(this.refs.inner).toEqual(inner);
89
- expect(this.refs.outer).toEqual(outer);
101
+ expect(this.refs.inner.getObject()).toEqual(innerObj);
102
+ expect(this.refs.outer.getObject()).toEqual(outerObj);
103
}
104
});
105
93
- var instance = <Component child={<span />} />;
106
+ var instance = <Component />;
107
instance = ReactTestUtils.renderIntoDocument(instance);
108
});
109
src/core/__tests__/ReactComponentLifeCycle-test.js
+9
-43
@@ -91,29 +91,23 @@ describe('ReactComponentLifeCycle', function() {
91
CompositeComponentLifeCycle = ReactCompositeComponent.LifeCycle;
92
});
93
94
- it('should scrub state when reinitialized using getInitialState', function() {
94
+ it('should not reuse an instance when it has been unmounted', function() {
95
+ var container = document.createElement('div');
96
var StatefulComponent = React.createClass({
97
getInitialState: function() {
98
return { };
99
},
99
- addAnotherField: function() {
100
- this.setState({
101
- aField: 'asdf'
102
- });
103
- },
100
render: function() {
101
return (
102
<div></div>
103
);
104
}
105
});
110
- var instance = <StatefulComponent />;
111
- instance = ReactTestUtils.renderIntoDocument(instance);
112
- instance.addAnotherField();
113
- expect(instance.state.aField).toBe('asdf');
114
- instance.unmountComponent();
115
- instance = ReactTestUtils.renderIntoDocument(instance);
116
- expect(typeof instance.state.aField).toBe('undefined');
106
+ var descriptor = <StatefulComponent />;
107
+ var firstInstance = React.renderComponent(descriptor, container);
108
+ React.unmountComponentAtNode(container);
109
+ var secondInstance = React.renderComponent(descriptor, container);
110
+ expect(firstInstance).not.toBe(secondInstance);
111
});
112
113
/**
@@ -165,29 +159,6 @@ describe('ReactComponentLifeCycle', function() {
159
]);
160
});
161
168
-
169
- it('should scrub state when re-initialized', function() {
170
- var StatefulComponent = React.createClass({
171
- addAnotherField: function() {
172
- this.setState({
173
- aField: 'asdf'
174
- });
175
- },
176
- render: function() {
177
- return (
178
- <div></div>
179
- );
180
- }
181
- });
182
- var instance = <StatefulComponent />;
183
- instance = ReactTestUtils.renderIntoDocument(instance);
184
- instance.addAnotherField();
185
- expect(instance.state.aField).toBe('asdf');
186
- instance.unmountComponent();
187
- instance = ReactTestUtils.renderIntoDocument(instance);
188
- expect(instance.state).toBe(null);
189
- });
190
-
162
// You could assign state here, but not access members of it, unless you
163
// had provided a getInitialState method.
164
it('throws when accessing state in componentWillMount', function() {
@@ -454,7 +425,7 @@ describe('ReactComponentLifeCycle', function() {
425
instance.setProps({text: "dos", tooltipText: "two"});
426
});
427
457
- it('should throw when calling setProps() on an unmounted component',
428
+ it('should not allow setProps() called on an unmounted descriptor',
429
function() {
430
var PropsToUpdate = React.createClass({
431
render: function() {
@@ -467,12 +438,7 @@ describe('ReactComponentLifeCycle', function() {
438
}
439
});
440
var instance = <PropsToUpdate value="hello" />;
470
- expect(function() {
471
- instance.setProps({value: "goodbye"});
472
- }).toThrow(
473
- 'Invariant Violation: replaceProps(...): Can only update a ' +
474
- 'mounted component.'
475
- );
441
+ expect(instance.setProps).not.toBeDefined();
442
});
443
444
it('should allow state updates in componentDidMount', function() {
src/core/__tests__/ReactCompositeComponent-test.js
+1
-39
@@ -402,12 +402,7 @@ describe('ReactCompositeComponent', function() {
402
});
403
404
var instance = <Component />;
405
- expect(function() {
406
- instance.forceUpdate();
407
- }).toThrow(
408
- 'Invariant Violation: forceUpdate(...): Can only force an update on ' +
409
- 'mounted or mounting components.'
410
- );
405
+ expect(instance.forceUpdate).not.toBeDefined();
406
407
instance = React.renderComponent(instance, container);
408
expect(function() {
@@ -1154,39 +1149,6 @@ describe('ReactCompositeComponent', function() {
1149
instance = ReactTestUtils.renderIntoDocument(instance);
1150
});
1151
1157
- it('should warn if an umounted component is touched', function() {
1158
- spyOn(console, 'warn');
1159
-
1160
- var ComponentClass = React.createClass({
1161
- getInitialState: function() {
1162
- return {valueToReturn: 'hi'};
1163
- },
1164
- someMethod: function() {
1165
- return this;
1166
- },
1167
- someOtherMethod: function() {
1168
- return this;
1169
- },
1170
- render: function() {
1171
- return <div></div>;
1172
- }
1173
- });
1174
-
1175
- var descriptor = <ComponentClass />;
1176
- var instance = ReactTestUtils.renderIntoDocument(descriptor);
1177
- instance.someMethod();
1178
- expect(console.warn.argsForCall.length).toBe(0);
1179
-
1180
- var unmountedInstance = <ComponentClass />;
1181
- unmountedInstance.someMethod();
1182
- expect(console.warn.argsForCall.length).toBe(1);
1183
-
1184
- var unmountedInstance2 = <ComponentClass />;
1185
- unmountedInstance2.someOtherMethod = 'override';
1186
- expect(console.warn.argsForCall.length).toBe(2);
1187
- expect(unmountedInstance2.someOtherMethod).toBe('override');
1188
- });
1189
-
1152
it('should allow static methods called using type property', function() {
1153
spyOn(console, 'warn');
1154
src/core/__tests__/ReactCompositeComponentMixin-test.js
+5
-5
@@ -129,8 +129,8 @@ describe('ReactCompositeComponent-mixin', function() {
129
});
130
131
it('should validate prop types via mixins', function() {
132
- expect(TestComponent.componentConstructor.propTypes).toBeDefined();
133
- expect(TestComponent.componentConstructor.propTypes.value)
132
+ expect(TestComponent.type.propTypes).toBeDefined();
133
+ expect(TestComponent.type.propTypes.value)
134
.toBe(mixinPropValidator);
135
});
136
@@ -138,11 +138,11 @@ describe('ReactCompositeComponent-mixin', function() {
138
// Sanity check...
139
expect(componentPropValidator).toNotBe(mixinPropValidator);
140
// Actually check...
141
- expect(TestComponentWithPropTypes.componentConstructor.propTypes)
141
+ expect(TestComponentWithPropTypes.type.propTypes)
142
.toBeDefined();
143
- expect(TestComponentWithPropTypes.componentConstructor.propTypes.value)
143
+ expect(TestComponentWithPropTypes.type.propTypes.value)
144
.toNotBe(mixinPropValidator);
145
- expect(TestComponentWithPropTypes.componentConstructor.propTypes.value)
145
+ expect(TestComponentWithPropTypes.type.propTypes.value)
146
.toBe(componentPropValidator);
147
});
148
});
src/core/__tests__/ReactCompositeComponentSpec-test.js
+3
-3
@@ -49,7 +49,7 @@ describe('ReactCompositeComponent-spec', function() {
49
}
50
});
51
52
- expect(TestComponent.componentConstructor.displayName)
52
+ expect(TestComponent.type.displayName)
53
.toBe('TestComponent');
54
});
55
@@ -64,8 +64,8 @@ describe('ReactCompositeComponent-spec', function() {
64
}
65
});
66
67
- expect(TestComponent.componentConstructor.propTypes).toBeDefined();
68
- expect(TestComponent.componentConstructor.propTypes.value)
67
+ expect(TestComponent.type.propTypes).toBeDefined();
68
+ expect(TestComponent.type.propTypes.value)
69
.toBe(propValidator);
70
});
71
});
src/core/__tests__/ReactIdentity-test.js
+15
-5
@@ -105,13 +105,23 @@ describe('ReactIdentity', function() {
105
});
106
107
function renderAComponentWithKeyIntoContainer(key, container) {
108
- var span1 = <span key={key} />;
109
- var span2 = <span />;
108
111
- var map = {};
112
- map[key] = span2;
109
+ var Wrapper = React.createClass({
110
+
111
+ render: function() {
112
+ var span1 = <span ref="span1" key={key} />;
113
+ var span2 = <span ref="span2" />;
114
+
115
+ var map = {};
116
+ map[key] = span2;
117
+ return <div>{[span1, map]}</div>;
118
+ }
119
+
120
+ });
121
114
- React.renderComponent(<div>{[span1, map]}</div>, container);
122
+ var instance = React.renderComponent(<Wrapper />, container);
123
+ var span1 = instance.refs.span1;
124
+ var span2 = instance.refs.span2;
125
126
expect(span1.getDOMNode()).not.toBe(null);
127
expect(span2.getDOMNode()).not.toBe(null);
src/core/instantiateReactComponent.js
+12
-20
@@ -19,7 +19,7 @@
19
20
"use strict";
21
22
-var warning = require('warning');
22
+var invariant = require('invariant');
23
24
/**
25
* Validate a `componentDescriptor`. This should be exposed publicly in a follow
@@ -30,10 +30,10 @@ var warning = require('warning');
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'
33
+ descriptor &&
34
+ typeof descriptor.type === 'function' &&
35
+ typeof descriptor.type.prototype.mountComponent === 'function' &&
36
+ typeof descriptor.type.prototype.receiveComponent === 'function'
37
);
38
}
39
@@ -47,24 +47,16 @@ function isValidComponentDescriptor(descriptor) {
47
* @protected
48
*/
49
function instantiateReactComponent(descriptor) {
50
- if (__DEV__) {
51
- warning(
50
+
51
+ // TODO: Make warning
52
+ // if (__DEV__) {
53
+ invariant(
54
isValidComponentDescriptor(descriptor),
55
'Only React Components are valid for mounting.'
56
);
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;
57
+ // }
58
+
59
+ return new descriptor.type(descriptor);
60
}
61
62
module.exports = instantiateReactComponent;
src/core/shouldUpdateReactComponent.js
+12
-29
@@ -20,40 +20,23 @@
20
"use strict";
21
22
/**
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.
23
+ * Given a `prevDescriptor` and `nextDescriptor`, determines if the existing
24
+ * instance should be updated as opposed to being destroyed or replaced by a new
25
+ * instance. Both arguments are descriptors. This ensures that this logic can
26
+ * operate on stateless trees without any backing instance.
27
*
29
- * @param {?object} prevComponentInstance
28
+ * @param {?object} prevDescriptor
29
* @param {?object} nextDescriptor
31
- * @return {boolean} True if `prevComponentInstance` should be updated.
30
+ * @return {boolean} True if the existing instance should be updated.
31
* @protected
32
*/
34
-function shouldUpdateReactComponent(prevComponentInstance, nextDescriptor) {
35
- // TODO: Remove warning after a release.
36
- if (prevComponentInstance && nextDescriptor &&
37
- prevComponentInstance.constructor === nextDescriptor.constructor && (
38
- (prevComponentInstance.props && prevComponentInstance.props.key) ===
33
+function shouldUpdateReactComponent(prevDescriptor, nextDescriptor) {
34
+ if (prevDescriptor && nextDescriptor &&
35
+ prevDescriptor.type === nextDescriptor.type && (
36
+ (prevDescriptor.props && prevDescriptor.props.key) ===
37
(nextDescriptor.props && nextDescriptor.props.key)
40
- )) {
41
- if (prevComponentInstance._owner === nextDescriptor._owner) {
42
- return true;
43
- } else {
44
- if (__DEV__) {
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.',
51
- prevComponentInstance,
52
- nextDescriptor
53
- );
54
- }
55
- }
56
- }
38
+ ) && prevDescriptor._owner === nextDescriptor._owner) {
39
+ return true;
40
}
41
return false;
42
}
src/test/ReactTestUtils.js
+32
-17
@@ -22,7 +22,7 @@ var EventConstants = require('EventConstants');
22
var EventPluginHub = require('EventPluginHub');
23
var EventPropagators = require('EventPropagators');
24
var React = require('React');
25
-var ReactComponent = require('ReactComponent');
25
+var ReactDescriptor = require('ReactDescriptor');
26
var ReactDOM = require('ReactDOM');
27
var ReactEventEmitter = require('ReactEventEmitter');
28
var ReactMount = require('ReactMount');
@@ -57,25 +57,39 @@ var ReactTestUtils = {
57
return React.renderComponent(instance, div);
58
},
59
60
- isComponentOfType: function(inst, convenienceConstructor) {
60
+ isDescriptor: function(descriptor) {
61
+ return ReactDescriptor.isValidDescriptor(descriptor);
62
+ },
63
+
64
+ isDescriptorOfType: function(inst, convenienceConstructor) {
65
return (
62
- ReactComponent.isValidComponent(inst) &&
66
+ ReactDescriptor.isValidDescriptor(inst) &&
67
inst.type === convenienceConstructor.type
68
);
69
},
70
71
isDOMComponent: function(inst) {
72
+ return !!(inst && inst.mountComponent && inst.tagName);
73
+ },
74
+
75
+ isDOMComponentDescriptor: function(inst) {
76
return !!(inst &&
69
- ReactComponent.isValidComponent(inst) &&
77
+ ReactDescriptor.isValidDescriptor(inst) &&
78
!!inst.tagName);
79
},
80
81
isCompositeComponent: function(inst) {
74
- if (!ReactComponent.isValidComponent(inst)) {
75
- return false;
76
- }
77
- if (inst.__realComponentInstance) {
78
- // This is a descriptor membrane
82
+ return typeof inst.render === 'function' &&
83
+ typeof inst.setState === 'function';
84
+ },
85
+
86
+ isCompositeComponentWithType: function(inst, type) {
87
+ return !!(ReactTestUtils.isCompositeComponent(inst) &&
88
+ (inst.constructor === type.type));
89
+ },
90
+
91
+ isCompositeComponentDescriptor: function(inst) {
92
+ if (!ReactDescriptor.isValidDescriptor(inst)) {
93
return false;
94
}
95
// We check the prototype of the type that will get mounted, not the
@@ -83,19 +97,17 @@ var ReactTestUtils = {
97
var prototype = inst.type.prototype;
98
return (
99
typeof prototype.render === 'function' &&
86
- typeof prototype.setState === 'function' &&
87
- typeof prototype.updateComponent === 'function'
100
+ typeof prototype.setState === 'function'
101
);
102
},
103
91
- isCompositeComponentWithType: function(inst, type) {
92
- return !!(ReactTestUtils.isCompositeComponent(inst) &&
93
- (inst.constructor === type.componentConstructor ||
94
- inst.constructor === type));
104
+ isCompositeComponentDescriptorWithType: function(inst, type) {
105
+ return !!(ReactTestUtils.isCompositeComponentDescriptor(inst) &&
106
+ (inst.constructor === type));
107
},
108
109
isTextComponent: function(inst) {
98
- return inst instanceof ReactTextComponent;
110
+ return inst instanceof ReactTextComponent.type;
111
},
112
113
findAllInRenderedTree: function(inst, test) {
@@ -186,7 +198,10 @@ var ReactTestUtils = {
198
*/
199
scryRenderedComponentsWithType: function(root, componentType) {
200
return ReactTestUtils.findAllInRenderedTree(root, function(inst) {
189
- return ReactTestUtils.isCompositeComponentWithType(inst, componentType);
201
+ return ReactTestUtils.isCompositeComponentWithType(
202
+ inst,
203
+ componentType
204
+ );
205
});
206
},
207
src/test/reactComponentExpect.js
+14
-20
@@ -17,7 +17,6 @@
17
* @nolint
18
*/
19
20
-var ReactComponent = require('ReactComponent');
20
var ReactTestUtils = require('ReactTestUtils');
21
22
var mergeInto = require('mergeInto');
@@ -32,7 +31,9 @@ function reactComponentExpect(instance) {
31
}
32
33
this._instance = instance;
35
- this.toBeValidReactComponent();
34
+ expect(typeof instance).toBe('object');
35
+ expect(typeof instance.constructor).toBe('function');
36
+ expect(ReactTestUtils.isDescriptor(instance)).toBe(false);
37
}
38
39
mergeInto(reactComponentExpect.prototype, {
@@ -105,10 +106,9 @@ mergeInto(reactComponentExpect.prototype, {
106
// Matchers ------------------------------------------------------------------
107
108
toBeComponentOfType: function(convenienceConstructor) {
108
- expect(ReactTestUtils.isComponentOfType(
109
- this.instance(),
110
- convenienceConstructor
111
- )).toBe(true);
109
+ expect(
110
+ this.instance().constructor === convenienceConstructor.type
111
+ ).toBe(true);
112
return this;
113
},
114
@@ -117,16 +117,18 @@ mergeInto(reactComponentExpect.prototype, {
117
* here.
118
*/
119
toBeCompositeComponent: function() {
120
- this.toBeValidReactComponent();
121
- expect(ReactTestUtils.isCompositeComponent(this.instance())).toBe(true);
120
+ expect(
121
+ typeof this.instance().render === 'function' &&
122
+ typeof this.instance().setState === 'function'
123
+ ).toBe(true);
124
return this;
125
},
126
127
toBeCompositeComponentWithType: function(convenienceConstructor) {
126
- expect(ReactTestUtils.isCompositeComponentWithType(
127
- this.instance(),
128
- convenienceConstructor
129
- )).toBe(true);
128
+ this.toBeCompositeComponent();
129
+ expect(
130
+ this.instance().constructor === convenienceConstructor.type
131
+ ).toBe(true);
132
return this;
133
},
134
@@ -135,14 +137,6 @@ mergeInto(reactComponentExpect.prototype, {
137
return this;
138
},
139
138
- /**
139
- * Falsy values are valid components - the vanished component that is.
140
- */
141
- toBeValidReactComponent: function() {
142
- expect(ReactComponent.isValidComponent(this.instance())).toBe(true);
143
- return this;
144
- },
145
-
140
toBePresent: function() {
141
expect(this.instance()).toBeTruthy();
142
return this;
src/utils/cloneWithProps.js
+3
-1
@@ -53,7 +53,9 @@ function cloneWithProps(child, props) {
53
newProps.children = child.props.children;
54
}
55
56
- return child.constructor.ConvenienceConstructor(newProps);
56
+ // The current API doesn't retain _owner and _context, which is why this
57
+ // doesn't use ReactDescriptor.cloneAndReplaceProps.
58
+ return child.constructor(newProps);
59
}
60
61
module.exports = cloneWithProps;
src/utils/onlyChild.js
+2
-2
@@ -17,7 +17,7 @@
17
*/
18
"use strict";
19
20
-var ReactComponent = require('ReactComponent');
20
+var ReactDescriptor = require('ReactDescriptor');
21
22
var invariant = require('invariant');
23
@@ -34,7 +34,7 @@ var invariant = require('invariant');
34
*/
35
function onlyChild(children) {
36
invariant(
37
- ReactComponent.isValidComponent(children),
37
+ ReactDescriptor.isValidDescriptor(children),
38
'onlyChild must be passed a children with exactly one child.'
39
);
40
return children;
src/utils/traverseAllChildren.js
+2
-2
@@ -153,11 +153,11 @@ var traverseAllChildrenImpl =
153
}
154
}
155
} else if (type === 'string') {
156
- var normalizedText = new ReactTextComponent(children);
156
+ var normalizedText = ReactTextComponent(children);
157
callback(traverseContext, normalizedText, storageName, indexSoFar);
158
subtreeCount += 1;
159
} else if (type === 'number') {
160
- var normalizedNumber = new ReactTextComponent('' + children);
160
+ var normalizedNumber = ReactTextComponent('' + children);
161
callback(traverseContext, normalizedNumber, storageName, indexSoFar);
162
subtreeCount += 1;
163
}