@samitouri / QOS-React-2 / commits / c40e06f728

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 }