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

Preparing to move defaultProps resolution and type validation to the descriptor

This copies the propType and contextType validation to a wrapper around the descriptor factory. By doing the validation early, we make it easier to track down bugs. It also prepares for static type checking which should be done at the usage site. This validation is not yet active and is just logged using monitorCodeUse. This will allow us to clean up callsites which would fail this new type of validation. I chose to copy the validation of abstracting it out since this is just an intermediate step to avoid spamming consoles. This makes more a much cleaner diff review/history. The original validation in the instance will be deleted as soon as we can turn on the warnings. Additionally, getDefaultProps are moved to become a static function which is only executed once. It should be moved to statics but we don't have a convenient way to merge mixins in statics right now. Deferring to ES6 classes. This is still a breaking change since you can return an object or array from getDefaultProps, which later gets mutated and now the shared instance is mutated. Mutating an object that is passed into you from props is highly discouraged and likely to lead to subtle bugs anyway. So I'm not too worried. The defaultProps will later be resolved in the descriptor factory. This will enable a perf optimizations where we don't create an unnecessary object allocation when you use default props. It also means that ReactChildren.map has access to resolved properties which gives them consistent behavior whether or not the default prop is specified.

Sebastian Markbage committed Jun 26, 2014 at 15:36 UTC d0719a5ea4843cca40b0c1a73ad9acffd9639b50
7 files changed +425 -222
src/browser/ReactDOM.js
+7
@@ -20,6 +20,7 @@
20 "use strict";
21
22 var ReactDescriptor = require('ReactDescriptor');
23 +var ReactDescriptorValidator = require('ReactDescriptorValidator');
24 var ReactDOMComponent = require('ReactDOMComponent');
25
26 var mergeInto = require('mergeInto');
@@ -50,6 +51,12 @@ function createDOMComponentClass(omitClose, tag) {
51
52 var ConvenienceConstructor = ReactDescriptor.createFactory(Constructor);
53
54 + if (__DEV__) {
55 + return ReactDescriptorValidator.createFactory(
56 + ConvenienceConstructor
57 + );
58 + }
59 +
60 return ConvenienceConstructor;
61 }
62
src/core/ReactCompositeComponent.js
+60 -39
@@ -22,6 +22,7 @@ var ReactComponent = require('ReactComponent');
22 var ReactContext = require('ReactContext');
23 var ReactCurrentOwner = require('ReactCurrentOwner');
24 var ReactDescriptor = require('ReactDescriptor');
25 +var ReactDescriptorValidator = require('ReactDescriptorValidator');
26 var ReactEmptyComponent = require('ReactEmptyComponent');
27 var ReactErrorUtils = require('ReactErrorUtils');
28 var ReactOwner = require('ReactOwner');
@@ -328,18 +329,17 @@ var ReactCompositeComponentInterface = {
329 * which all other static methods are defined.
330 */
331 var RESERVED_SPEC_KEYS = {
331 - displayName: function(ConvenienceConstructor, displayName) {
332 - ConvenienceConstructor.type.displayName = displayName;
332 + displayName: function(Constructor, displayName) {
333 + Constructor.displayName = displayName;
334 },
334 - mixins: function(ConvenienceConstructor, mixins) {
335 + mixins: function(Constructor, mixins) {
336 if (mixins) {
337 for (var i = 0; i < mixins.length; i++) {
337 - mixSpecIntoComponent(ConvenienceConstructor, mixins[i]);
338 + mixSpecIntoComponent(Constructor, mixins[i]);
339 }
340 }
341 },
341 - childContextTypes: function(ConvenienceConstructor, childContextTypes) {
342 - var Constructor = ConvenienceConstructor.type;
342 + childContextTypes: function(Constructor, childContextTypes) {
343 validateTypeDef(
344 Constructor,
345 childContextTypes,
@@ -350,8 +350,7 @@ var RESERVED_SPEC_KEYS = {
350 childContextTypes
351 );
352 },
353 - contextTypes: function(ConvenienceConstructor, contextTypes) {
354 - var Constructor = ConvenienceConstructor.type;
353 + contextTypes: function(Constructor, contextTypes) {
354 validateTypeDef(
355 Constructor,
356 contextTypes,
@@ -359,8 +358,21 @@ var RESERVED_SPEC_KEYS = {
358 );
359 Constructor.contextTypes = merge(Constructor.contextTypes, contextTypes);
360 },
362 - propTypes: function(ConvenienceConstructor, propTypes) {
363 - var Constructor = ConvenienceConstructor.type;
361 + /**
362 + * Special case getDefaultProps which should move into statics but requires
363 + * automatic merging.
364 + */
365 + getDefaultProps: function(Constructor, getDefaultProps) {
366 + if (Constructor.getDefaultProps) {
367 + Constructor.getDefaultProps = createMergedResultFunction(
368 + Constructor.getDefaultProps,
369 + getDefaultProps
370 + );
371 + } else {
372 + Constructor.getDefaultProps = getDefaultProps;
373 + }
374 + },
375 + propTypes: function(Constructor, propTypes) {
376 validateTypeDef(
377 Constructor,
378 propTypes,
@@ -368,8 +380,8 @@ var RESERVED_SPEC_KEYS = {
380 );
381 Constructor.propTypes = merge(Constructor.propTypes, propTypes);
382 },
371 - statics: function(ConvenienceConstructor, statics) {
372 - mixStaticSpecIntoComponent(ConvenienceConstructor, statics);
383 + statics: function(Constructor, statics) {
384 + mixStaticSpecIntoComponent(Constructor, statics);
385 }
386 };
387
@@ -439,7 +451,7 @@ function validateLifeCycleOnReplaceState(instance) {
451 * Custom version of `mixInto` which handles policy validation and reserved
452 * specification keys when building `ReactCompositeComponent` classses.
453 */
442 -function mixSpecIntoComponent(ConvenienceConstructor, spec) {
454 +function mixSpecIntoComponent(Constructor, spec) {
455 invariant(
456 !ReactDescriptor.isValidFactory(spec),
457 'ReactCompositeComponent: You\'re attempting to ' +
@@ -451,7 +463,6 @@ function mixSpecIntoComponent(ConvenienceConstructor, spec) {
463 'use a component as a mixin. Instead, just use a regular object.'
464 );
465
454 - var Constructor = ConvenienceConstructor.type;
466 var proto = Constructor.prototype;
467 for (var name in spec) {
468 var property = spec[name];
@@ -462,7 +473,7 @@ function mixSpecIntoComponent(ConvenienceConstructor, spec) {
473 validateMethodOverride(proto, name);
474
475 if (RESERVED_SPEC_KEYS.hasOwnProperty(name)) {
465 - RESERVED_SPEC_KEYS[name](ConvenienceConstructor, property);
476 + RESERVED_SPEC_KEYS[name](Constructor, property);
477 } else {
478 // Setup methods on prototype:
479 // The following member methods should not be automatically bound:
@@ -523,7 +534,7 @@ function mixSpecIntoComponent(ConvenienceConstructor, spec) {
534 }
535 }
536
526 -function mixStaticSpecIntoComponent(ConvenienceConstructor, statics) {
537 +function mixStaticSpecIntoComponent(Constructor, statics) {
538 if (!statics) {
539 return;
540 }
@@ -533,10 +544,10 @@ function mixStaticSpecIntoComponent(ConvenienceConstructor, statics) {
544 continue;
545 }
546
536 - var isInherited = name in ConvenienceConstructor;
547 + var isInherited = name in Constructor;
548 var result = property;
549 if (isInherited) {
539 - var existingProperty = ConvenienceConstructor[name];
550 + var existingProperty = Constructor[name];
551 var existingType = typeof existingProperty;
552 var propertyType = typeof property;
553 invariant(
@@ -549,10 +560,7 @@ function mixStaticSpecIntoComponent(ConvenienceConstructor, statics) {
560 );
561 result = createChainedFunction(existingProperty, property);
562 }
552 - ConvenienceConstructor[name] = typeof result === 'function' ?
553 - result.bind(ConvenienceConstructor.type) :
554 - result;
555 - ConvenienceConstructor.type[name] = result;
563 + Constructor[name] = result;
564 }
565 }
566
@@ -731,7 +739,6 @@ var ReactCompositeComponentMixin = {
739 }
740
741 this.context = this._processContext(this._descriptor._context);
734 - this._defaultProps = this.getDefaultProps ? this.getDefaultProps() : null;
742 this.props = this._processProps(this.props);
743
744 this.state = this.getInitialState ? this.getInitialState() : null;
@@ -785,8 +792,6 @@ var ReactCompositeComponentMixin = {
792 }
793 this._compositeLifeCycleState = null;
794
788 - this._defaultProps = null;
789 -
795 this._renderedComponent.unmountComponent();
796 this._renderedComponent = null;
797
@@ -924,13 +929,17 @@ var ReactCompositeComponentMixin = {
929 * @private
930 */
931 _processProps: function(newProps) {
927 - var props = merge(newProps);
928 - var defaultProps = this._defaultProps;
929 - for (var propName in defaultProps) {
930 - if (!props.hasOwnProperty(propName) ||
931 - typeof props[propName] === 'undefined') {
932 - props[propName] = defaultProps[propName];
932 + var defaultProps = this.constructor.defaultProps;
933 + var props;
934 + if (defaultProps) {
935 + props = merge(newProps);
936 + for (var propName in defaultProps) {
937 + if (typeof props[propName] === 'undefined') {
938 + props[propName] = defaultProps[propName];
939 + }
940 }
941 + } else {
942 + props = newProps;
943 }
944 if (__DEV__) {
945 var propTypes = this.constructor.propTypes;
@@ -950,6 +959,8 @@ var ReactCompositeComponentMixin = {
959 * @private
960 */
961 _checkPropTypes: function(propTypes, props, location) {
962 + // TODO: Stop validating prop types here and only use the descriptor
963 + // validation.
964 var componentName = this.constructor.displayName;
965 for (var propName in propTypes) {
966 if (propTypes.hasOwnProperty(propName)) {
@@ -1325,16 +1336,16 @@ var ReactCompositeComponent = {
1336 Constructor.prototype = new ReactCompositeComponentBase();
1337 Constructor.prototype.constructor = Constructor;
1338
1328 - var ConvenienceConstructor = ReactDescriptor.createFactory(Constructor);
1329 -
1330 - // TODO: Move statics off of the convenience constructor. That way the
1331 - // factory can be created independently from the main class.
1332 -
1339 injectedMixins.forEach(
1334 - mixSpecIntoComponent.bind(null, ConvenienceConstructor)
1340 + mixSpecIntoComponent.bind(null, Constructor)
1341 );
1342
1337 - mixSpecIntoComponent(ConvenienceConstructor, spec);
1343 + mixSpecIntoComponent(Constructor, spec);
1344 +
1345 + // Initialize the defaultProps property after all mixins have been merged
1346 + if (Constructor.getDefaultProps) {
1347 + Constructor.defaultProps = Constructor.getDefaultProps();
1348 + }
1349
1350 invariant(
1351 Constructor.prototype.render,
@@ -1363,7 +1374,17 @@ var ReactCompositeComponent = {
1374 }
1375 }
1376
1366 - return ConvenienceConstructor;
1377 + var descriptorFactory = ReactDescriptor.createFactory(Constructor);
1378 +
1379 + if (__DEV__) {
1380 + return ReactDescriptorValidator.createFactory(
1381 + descriptorFactory,
1382 + Constructor.propTypes,
1383 + Constructor.contextTypes
1384 + );
1385 + }
1386 +
1387 + return descriptorFactory;
1388 },
1389
1390 injection: {
src/core/ReactDescriptor.js
+38 -166
@@ -21,165 +21,9 @@
21 var ReactContext = require('ReactContext');
22 var ReactCurrentOwner = require('ReactCurrentOwner');
23
24 -var monitorCodeUse = require('monitorCodeUse');
24 +var merge = require('merge');
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 ownerHasKeyUseWarning = {
33 - 'react_key_warning': {},
34 - 'react_numeric_key_warning': {}
35 -};
36 -var ownerHasMonitoredObjectMap = {};
37 -
38 -var NUMERIC_PROPERTY_REGEX = /^\d+$/;
39 -
40 -/**
41 - * Gets the current owner's displayName for use in warnings.
42 - *
43 - * @internal
44 - * @return {?string} Display name or undefined
45 - */
46 -function getCurrentOwnerDisplayName() {
47 - var current = ReactCurrentOwner.current;
48 - return current && current.constructor.displayName || undefined;
49 -}
50 -
51 -/**
52 - * Warn if the component doesn't have an explicit key assigned to it.
53 - * This component is in an array. The array could grow and shrink or be
54 - * reordered. All children that haven't already been validated are required to
55 - * have a "key" property assigned to it.
56 - *
57 - * @internal
58 - * @param {ReactComponent} component Component that requires a key.
59 - * @param {*} parentType component's parent's type.
60 - */
61 -function validateExplicitKey(component, parentType) {
62 - if (component._store.validated || component.props.key != null) {
63 - return;
64 - }
65 - component._store.validated = true;
66 -
67 - warnAndMonitorForKeyUse(
68 - 'react_key_warning',
69 - 'Each child in an array should have a unique "key" prop.',
70 - component,
71 - parentType
72 - );
73 -}
74 -
75 -/**
76 - * Warn if the key is being defined as an object property but has an incorrect
77 - * value.
78 - *
79 - * @internal
80 - * @param {string} name Property name of the key.
81 - * @param {ReactComponent} component Component that requires a key.
82 - * @param {*} parentType component's parent's type.
83 - */
84 -function validatePropertyKey(name, component, parentType) {
85 - if (!NUMERIC_PROPERTY_REGEX.test(name)) {
86 - return;
87 - }
88 - warnAndMonitorForKeyUse(
89 - 'react_numeric_key_warning',
90 - 'Child objects should have non-numeric keys so ordering is preserved.',
91 - component,
92 - parentType
93 - );
94 -}
95 -
96 -/**
97 - * Shared warning and monitoring code for the key warnings.
98 - *
99 - * @internal
100 - * @param {string} warningID The id used when logging.
101 - * @param {string} message The base warning that gets output.
102 - * @param {ReactComponent} component Component that requires a key.
103 - * @param {*} parentType component's parent's type.
104 - */
105 -function warnAndMonitorForKeyUse(warningID, message, component, parentType) {
106 - var ownerName = getCurrentOwnerDisplayName();
107 - var parentName = parentType.displayName;
108 -
109 - var useName = ownerName || parentName;
110 - var memoizer = ownerHasKeyUseWarning[warningID];
111 - if (memoizer.hasOwnProperty(useName)) {
112 - return;
113 - }
114 - memoizer[useName] = true;
115 -
116 - message += ownerName ?
117 - ` Check the render method of ${ownerName}.` :
118 - ` Check the renderComponent call using <${parentName}>.`;
119 -
120 - // Usually the current owner is the offender, but if it accepts children as a
121 - // property, it may be the creator of the child that's responsible for
122 - // assigning it a key.
123 - var childOwnerName = null;
124 - if (component._owner && component._owner !== ReactCurrentOwner.current) {
125 - // Name of the component that originally created this child.
126 - childOwnerName = component._owner.constructor.displayName;
127 -
128 - message += ` It was passed a child from ${childOwnerName}.`;
129 - }
130 -
131 - message += ' See http://fb.me/react-warning-keys for more information.';
132 - monitorCodeUse(warningID, {
133 - component: useName,
134 - componentOwner: childOwnerName
135 - });
136 - console.warn(message);
137 -}
138 -
139 -/**
140 - * Log that we're using an object map. We're considering deprecating this
141 - * feature and replace it with proper Map and ImmutableMap data structures.
142 - *
143 - * @internal
144 - */
145 -function monitorUseOfObjectMap() {
146 - var currentName = getCurrentOwnerDisplayName() || '';
147 - if (ownerHasMonitoredObjectMap.hasOwnProperty(currentName)) {
148 - return;
149 - }
150 - ownerHasMonitoredObjectMap[currentName] = true;
151 - monitorCodeUse('react_object_map_children');
152 -}
153 -
154 -/**
155 - * Ensure that every component either is passed in a static location, in an
156 - * array with an explicit keys property defined, or in an object literal
157 - * with valid key property.
158 - *
159 - * @internal
160 - * @param {*} component Statically passed child of any type.
161 - * @param {*} parentType component's parent's type.
162 - * @return {boolean}
163 - */
164 -function validateChildKeys(component, parentType) {
165 - if (Array.isArray(component)) {
166 - for (var i = 0; i < component.length; i++) {
167 - var child = component[i];
168 - if (ReactDescriptor.isValidDescriptor(child)) {
169 - validateExplicitKey(child, parentType);
170 - }
171 - }
172 - } else if (ReactDescriptor.isValidDescriptor(component)) {
173 - // This component was passed in a valid location.
174 - component._store.validated = true;
175 - } else if (component && typeof component === 'object') {
176 - monitorUseOfObjectMap();
177 - for (var name in component) {
178 - validatePropertyKey(name, component[name], parentType);
179 - }
180 - }
181 -}
182 -
27 /**
28 * Warn for mutations.
29 *
@@ -237,6 +81,26 @@ function defineMutationMembrane(prototype) {
81 }
82 }
83
84 +/**
85 + * Transfer static properties from the source to the target. Functions are
86 + * rebound to have this reflect the original source.
87 + */
88 +function proxyStaticMethods(target, source) {
89 + if (typeof source !== 'function') {
90 + return;
91 + }
92 + for (var key in source) {
93 + if (source.hasOwnProperty(key)) {
94 + var value = source[key];
95 + if (typeof value === 'function') {
96 + target[key] = value.bind(source);
97 + } else {
98 + target[key] = value;
99 + }
100 + }
101 + }
102 +}
103 +
104 /**
105 * Base constructor for all React descriptors. This is only used to make this
106 * work with a dynamic instanceof check. Nothing should live on this prototype.
@@ -255,28 +119,32 @@ ReactDescriptor.createFactory = function(type) {
119 var descriptorPrototype = Object.create(ReactDescriptor.prototype);
120
121 var factory = function(props, children) {
122 + // For consistency we currently allocate a new object for every descriptor.
123 + // This protects the descriptor from being mutated by the original props
124 + // object being mutated. It also protects the original props object from
125 + // being mutated by children arguments and default props. This behavior
126 + // comes with a performance cost and could be deprecated in the future.
127 + // It could also be optimized with a smarter JSX transform.
128 if (props == null) {
129 props = {};
130 + } else if (typeof props === 'object') {
131 + props = merge(props);
132 }
133
262 - // Children can be more than one argument
134 + // Children can be more than one argument, and those are transferred onto
135 + // the newly allocated props object.
136 var childrenLength = arguments.length - 1;
137 if (childrenLength === 1) {
265 - if (__DEV__) {
266 - validateChildKeys(children, type);
267 - }
138 props.children = children;
139 } else if (childrenLength > 1) {
140 var childArray = Array(childrenLength);
141 for (var i = 0; i < childrenLength; i++) {
272 - if (__DEV__) {
273 - validateChildKeys(arguments[i + 1], type);
274 - }
142 childArray[i] = arguments[i + 1];
143 }
144 props.children = childArray;
145 }
146
147 + // Initialize the descriptor object
148 var descriptor = Object.create(descriptorPrototype);
149
150 // Record the component responsible for creating this descriptor.
@@ -289,10 +157,13 @@ ReactDescriptor.createFactory = function(type) {
157 if (__DEV__) {
158 // The validation flag and props are currently mutative. We put them on
159 // an external backing store so that we can freeze the whole object.
160 + // This can be replaced with a WeakMap once they are implemented in
161 + // commonly used development environments.
162 descriptor._store = { validated: false, props: props };
163
164 // We're not allowed to set props directly on the object so we early
295 - // return and rely on the prototype membrane toward to the backing store.
165 + // return and rely on the prototype membrane to forward to the backing
166 + // store.
167 if (useMutationMembrane) {
168 Object.freeze(descriptor);
169 return descriptor;
@@ -301,7 +172,6 @@ ReactDescriptor.createFactory = function(type) {
172
173 descriptor.props = props;
174 return descriptor;
304 -
175 };
176
177 // Currently we expose the prototype of the descriptor so that
@@ -316,6 +186,8 @@ ReactDescriptor.createFactory = function(type) {
186 factory.type = type;
187 descriptorPrototype.type = type;
188
189 + proxyStaticMethods(factory, type);
190 +
191 // Expose a unique constructor on the prototype is that this works with type
192 // systems that compare constructor properties: <Foo />.constructor === Foo
193 // This may be controversial since it requires a known factory function.
src/core/ReactDescriptorValidator.js new
+283
@@ -0,0 +1,283 @@
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 ReactDescriptorValidator
17 + */
18 +
19 +/**
20 + * ReactDescriptorValidator provides a wrapper around a descriptor factory
21 + * which validates the props passed to the descriptor. This is intended to be
22 + * used only in DEV and could be replaced by a static type checker for languages
23 + * that support it.
24 + */
25 +
26 +"use strict";
27 +
28 +var ReactDescriptor = require('ReactDescriptor');
29 +var ReactPropTypeLocations = require('ReactPropTypeLocations');
30 +var ReactCurrentOwner = require('ReactCurrentOwner');
31 +
32 +var monitorCodeUse = require('monitorCodeUse');
33 +
34 +/**
35 + * Warn if there's no key explicitly set on dynamic arrays of children or
36 + * object keys are not valid. This allows us to keep track of children between
37 + * updates.
38 + */
39 +var ownerHasKeyUseWarning = {
40 + 'react_key_warning': {},
41 + 'react_numeric_key_warning': {}
42 +};
43 +var ownerHasMonitoredObjectMap = {};
44 +
45 +var loggedTypeFailures = {};
46 +
47 +var NUMERIC_PROPERTY_REGEX = /^\d+$/;
48 +
49 +/**
50 + * Gets the current owner's displayName for use in warnings.
51 + *
52 + * @internal
53 + * @return {?string} Display name or undefined
54 + */
55 +function getCurrentOwnerDisplayName() {
56 + var current = ReactCurrentOwner.current;
57 + return current && current.constructor.displayName || undefined;
58 +}
59 +
60 +/**
61 + * Warn if the component doesn't have an explicit key assigned to it.
62 + * This component is in an array. The array could grow and shrink or be
63 + * reordered. All children that haven't already been validated are required to
64 + * have a "key" property assigned to it.
65 + *
66 + * @internal
67 + * @param {ReactComponent} component Component that requires a key.
68 + * @param {*} parentType component's parent's type.
69 + */
70 +function validateExplicitKey(component, parentType) {
71 + if (component._store.validated || component.props.key != null) {
72 + return;
73 + }
74 + component._store.validated = true;
75 +
76 + warnAndMonitorForKeyUse(
77 + 'react_key_warning',
78 + 'Each child in an array should have a unique "key" prop.',
79 + component,
80 + parentType
81 + );
82 +}
83 +
84 +/**
85 + * Warn if the key is being defined as an object property but has an incorrect
86 + * value.
87 + *
88 + * @internal
89 + * @param {string} name Property name of the key.
90 + * @param {ReactComponent} component Component that requires a key.
91 + * @param {*} parentType component's parent's type.
92 + */
93 +function validatePropertyKey(name, component, parentType) {
94 + if (!NUMERIC_PROPERTY_REGEX.test(name)) {
95 + return;
96 + }
97 + warnAndMonitorForKeyUse(
98 + 'react_numeric_key_warning',
99 + 'Child objects should have non-numeric keys so ordering is preserved.',
100 + component,
101 + parentType
102 + );
103 +}
104 +
105 +/**
106 + * Shared warning and monitoring code for the key warnings.
107 + *
108 + * @internal
109 + * @param {string} warningID The id used when logging.
110 + * @param {string} message The base warning that gets output.
111 + * @param {ReactComponent} component Component that requires a key.
112 + * @param {*} parentType component's parent's type.
113 + */
114 +function warnAndMonitorForKeyUse(warningID, message, component, parentType) {
115 + var ownerName = getCurrentOwnerDisplayName();
116 + var parentName = parentType.displayName;
117 +
118 + var useName = ownerName || parentName;
119 + var memoizer = ownerHasKeyUseWarning[warningID];
120 + if (memoizer.hasOwnProperty(useName)) {
121 + return;
122 + }
123 + memoizer[useName] = true;
124 +
125 + message += ownerName ?
126 + ` Check the render method of ${ownerName}.` :
127 + ` Check the renderComponent call using <${parentName}>.`;
128 +
129 + // Usually the current owner is the offender, but if it accepts children as a
130 + // property, it may be the creator of the child that's responsible for
131 + // assigning it a key.
132 + var childOwnerName = null;
133 + if (component._owner && component._owner !== ReactCurrentOwner.current) {
134 + // Name of the component that originally created this child.
135 + childOwnerName = component._owner.constructor.displayName;
136 +
137 + message += ` It was passed a child from ${childOwnerName}.`;
138 + }
139 +
140 + message += ' See http://fb.me/react-warning-keys for more information.';
141 + monitorCodeUse(warningID, {
142 + component: useName,
143 + componentOwner: childOwnerName
144 + });
145 + console.warn(message);
146 +}
147 +
148 +/**
149 + * Log that we're using an object map. We're considering deprecating this
150 + * feature and replace it with proper Map and ImmutableMap data structures.
151 + *
152 + * @internal
153 + */
154 +function monitorUseOfObjectMap() {
155 + var currentName = getCurrentOwnerDisplayName() || '';
156 + if (ownerHasMonitoredObjectMap.hasOwnProperty(currentName)) {
157 + return;
158 + }
159 + ownerHasMonitoredObjectMap[currentName] = true;
160 + monitorCodeUse('react_object_map_children');
161 +}
162 +
163 +/**
164 + * Ensure that every component either is passed in a static location, in an
165 + * array with an explicit keys property defined, or in an object literal
166 + * with valid key property.
167 + *
168 + * @internal
169 + * @param {*} component Statically passed child of any type.
170 + * @param {*} parentType component's parent's type.
171 + * @return {boolean}
172 + */
173 +function validateChildKeys(component, parentType) {
174 + if (Array.isArray(component)) {
175 + for (var i = 0; i < component.length; i++) {
176 + var child = component[i];
177 + if (ReactDescriptor.isValidDescriptor(child)) {
178 + validateExplicitKey(child, parentType);
179 + }
180 + }
181 + } else if (ReactDescriptor.isValidDescriptor(component)) {
182 + // This component was passed in a valid location.
183 + component._store.validated = true;
184 + } else if (component && typeof component === 'object') {
185 + monitorUseOfObjectMap();
186 + for (var name in component) {
187 + validatePropertyKey(name, component[name], parentType);
188 + }
189 + }
190 +}
191 +
192 +/**
193 + * Assert that the props are valid
194 + *
195 + * @param {string} componentName Name of the component for error messages.
196 + * @param {object} propTypes Map of prop name to a ReactPropType
197 + * @param {object} props
198 + * @param {string} location e.g. "prop", "context", "child context"
199 + * @private
200 + */
201 +function checkPropTypes(componentName, propTypes, props, location) {
202 + for (var propName in propTypes) {
203 + if (propTypes.hasOwnProperty(propName)) {
204 + var error;
205 + // Prop type validation may throw. In case they do, we don't want to
206 + // fail the render phase where it didn't fail before. So we log it.
207 + // After these have been cleaned up, we'll let them throw.
208 + try {
209 + error = propTypes[propName](props, propName, componentName, location);
210 + } catch (ex) {
211 + error = ex;
212 + }
213 + if (error instanceof Error && !(error.message in loggedTypeFailures)) {
214 + // Only monitor this failure once because there tends to be a lot of the
215 + // same error.
216 + loggedTypeFailures[error.message] = true;
217 + // This will soon use the warning module
218 + monitorCodeUse(
219 + 'react_failed_descriptor_type_check',
220 + { message: error.message }
221 + );
222 + }
223 + }
224 + }
225 +}
226 +
227 +var ReactDescriptorValidator = {
228 +
229 + /**
230 + * Wraps a descriptor factory function in another function which validates
231 + * the props and context of the descriptor and warns about any failed type
232 + * checks.
233 + *
234 + * @param {function} factory The original descriptor factory
235 + * @param {object?} propTypes A prop type definition set
236 + * @param {object?} contextTypes A context type definition set
237 + * @return {object} The component descriptor, which may be invalid.
238 + * @private
239 + */
240 + createFactory: function(factory, propTypes, contextTypes) {
241 + var validatedFactory = function(props, children) {
242 + var descriptor = factory.apply(this, arguments);
243 +
244 + for (var i = 1; i < arguments.length; i++) {
245 + validateChildKeys(arguments[i], descriptor.type);
246 + }
247 +
248 + var name = descriptor.type.displayName;
249 + if (propTypes) {
250 + checkPropTypes(
251 + name,
252 + propTypes,
253 + descriptor.props,
254 + ReactPropTypeLocations.prop
255 + );
256 + }
257 + if (contextTypes) {
258 + checkPropTypes(
259 + name,
260 + contextTypes,
261 + descriptor._context,
262 + ReactPropTypeLocations.context
263 + );
264 + }
265 + return descriptor;
266 + };
267 +
268 + validatedFactory.prototype = factory.prototype;
269 + validatedFactory.type = factory.type;
270 +
271 + // Copy static properties
272 + for (var key in factory) {
273 + if (factory.hasOwnProperty(key)) {
274 + validatedFactory[key] = factory[key];
275 + }
276 + }
277 +
278 + return validatedFactory;
279 + }
280 +
281 +};
282 +
283 +module.exports = ReactDescriptorValidator;
src/core/__tests__/ReactCompositeComponent-test.js
+5 -15
@@ -376,27 +376,17 @@ describe('ReactCompositeComponent', function() {
376
377 });
378
379 - it('should auto bind before getDefaultProps', function() {
380 - var calls = 0;
379 + it('should not pass this to getDefaultProps', function() {
380 var Component = React.createClass({
381 getDefaultProps: function() {
383 - return {
384 - onClick: this.defaultClickHandler
385 - };
386 - },
387 - defaultClickHandler: function() {
388 - expect(this).toBe(instance);
389 - calls++;
382 + expect(this.render).not.toBeDefined();
383 + return {};
384 },
385 render: function() {
392 - return <div onClick={this.props.onClick}></div>;
386 + return <div />;
387 }
388 });
395 - var instance = ReactTestUtils.renderIntoDocument(<Component />);
396 - var handler = instance.props.onClick;
397 - // Call handler with no context
398 - handler();
399 - expect(calls).toBe(1);
389 + ReactTestUtils.renderIntoDocument(<Component />);
390 });
391
392 it('should use default values for undefined props', function() {
src/core/__tests__/ReactPropTransferer-test.js
+30
@@ -156,4 +156,34 @@ describe('ReactPropTransferer', function() {
156 'passed in as props or children.'
157 );
158 });
159 +
160 + it('should not use the default when a prop is transfered', function() {
161 +
162 + var Child = React.createClass({
163 +
164 + getDefaultProps: function() {
165 + return {
166 + x: 2
167 + };
168 + },
169 +
170 + render: function() {
171 + expect(this.props.x).toBe(5);
172 + return <div />;
173 + }
174 +
175 + });
176 +
177 + var Parent = React.createClass({
178 +
179 + render: function() {
180 + return this.transferPropsTo(<Child />);
181 + }
182 +
183 + });
184 +
185 + ReactTestUtils.renderIntoDocument(<Parent x={5} />);
186 +
187 + });
188 +
189 });
src/core/__tests__/ReactPropTypes-test.js
+2 -2
@@ -712,7 +712,7 @@ describe('Custom validator', function() {
712 var instance = <Component num={5} />;
713 instance = ReactTestUtils.renderIntoDocument(instance);
714
715 - expect(spy.argsForCall.length).toBe(1);
715 + expect(spy.argsForCall.length).toBe(2); // temp double validation
716 expect(spy.argsForCall[0][1]).toBe('num');
717 expect(spy.argsForCall[0][2]).toBe('Component');
718 });
@@ -730,7 +730,7 @@ describe('Custom validator', function() {
730 var instance = <Component bla={5} />;
731 instance = ReactTestUtils.renderIntoDocument(instance);
732
733 - expect(spy.argsForCall.length).toBe(1);
733 + expect(spy.argsForCall.length).toBe(2); // temp double validation
734 });
735
736 it('should have received the validator\'s return value', function() {