37
*/
38
var OWNER = '{owner}';
39
40
+/**
41
+ * Props key that determines if a component's key was already validated.
42
+ * @private
43
+ */
44
+var IS_KEY_VALIDATED = '{is.key.validated}';
45
+
46
/**
47
* Every React component is in one of these life cycles.
48
*/
63
* This allows us to keep track of children between updates.
64
*/
65
60
-var CHILD_HAS_NO_IDENTITY =
61
- 'Each child in an array should have a unique "key" prop. ' +
62
- 'Check the render method of ';
63
-
64
-var CHILD_CAME_FROM_ANOTHER_OWNER = '. It was passed a child from ';
65
-
66
var ownerHasWarned = {};
67
68
/**
69
- * Helpers for flattening child arguments onto a new array or use an existing
70
- * one.
71
- */
72
-
73
-/**
74
- * Generate a unique key that identifies this child within a set.
69
+ * Warn if the component doesn't have an explicit key assigned to it.
70
+ * This component is in an array. The array could grow and shrink or be
71
+ * reordered. All children, that hasn't already been validated, are required to
72
+ * have a "key" property assigned to it.
73
*
76
- * @param {*} Manually provided key.
77
- * @param {number} Index that is used if a manual key is not provided.
78
- * @param {?number} Grouping index if this is used in a nested array.
79
- * @return {string}
80
- */
81
-function createKey(explicitKey, childIndex, groupingIndex) {
82
- return ReactCurrentOwner.getDepth() + ':' +
83
- (groupingIndex == null ? '' : groupingIndex + ':') +
84
- (explicitKey == null ? '' + childIndex : explicitKey);
85
-}
86
-
87
-/**
88
- * Returns true if this parameter type is considered an empty child slot.
89
- * Used to filter out empty slots and generate a compact array.
90
- *
91
- * @param {*} Child component or any value.
92
- * @return {boolean}
74
+ * @internal
75
+ * @param {ReactComponent} component Component that requires a key.
76
*/
94
-function isEmptyChild(child) {
95
- return child == null || typeof child === 'boolean';
96
-}
77
+function validateExplicitKey(component) {
78
+ if (component[IS_KEY_VALIDATED] || component.props.key != null) {
79
+ return;
80
+ }
81
+ component[IS_KEY_VALIDATED] = true;
82
98
-/**
99
- * Assign an internal identity to a child component.
100
- *
101
- * @param {number} Index of the current array grouping.
102
- * @param {*} Child component or any value.
103
- * @param {number} Index of the current child within it's grouping.
104
- */
105
-function assignKey(groupingIndex, child, index) {
106
- // Only truthy internal keys are valid. If it's not, we assign one.
107
- if (ReactComponent.isValidComponent(child) && !child._key) {
108
- var key = child.props.key;
109
- if (__DEV__) {
110
- // This is in an array. This could grow and shrink or be reordered.
111
- // All children that doesn't already have autogenerated identity needs
112
- // to have an explicit key provided.
113
- if (key == null) {
114
- // Name of the component whose render method tried to pass children.
115
- var currentName =
116
- ReactCurrentOwner.current &&
117
- ReactCurrentOwner.current.constructor.displayName;
118
-
119
- // Name of the component that originally created this child.
120
- var childOwnerName =
121
- child.props[OWNER] && child.props[OWNER].constructor.displayName;
122
-
123
- if (currentName && !ownerHasWarned.hasOwnProperty(currentName)) {
124
- ownerHasWarned[currentName] = true;
125
- var message = CHILD_HAS_NO_IDENTITY + currentName;
126
- if (childOwnerName && currentName !== childOwnerName) {
127
- // Usually the current owner is the offender, but if it accepts
128
- // children as a property, it may be the creator of the child that's
129
- // responsible for assigning it a key.
130
- message += CHILD_CAME_FROM_ANOTHER_OWNER + childOwnerName;
131
- }
132
- message += '.';
133
- console && console.warn && console.warn(message);
134
- }
135
- }
136
- }
137
- child._key = createKey(key, index, groupingIndex);
83
+ // We can't provide friendly warnings for top level components.
84
+ if (!ReactCurrentOwner.current) {
85
+ return;
86
}
139
-}
87
141
-/**
142
- * Make sure all children have an internal identity. Returns true if this is
143
- * already a compact array.
144
- *
145
- * @param {array} Children of any type.
146
- * @return {boolean}
147
- */
148
-function tryToReuseArray(children) {
149
- for (var i = 0; i < children.length; i++) {
150
- var child = children[i];
151
- if (isEmptyChild(child)) {
152
- return false;
153
- }
154
- assignKey(0, child, i);
88
+ // Name of the component whose render method tried to pass children.
89
+ var currentName = ReactCurrentOwner.current.constructor.displayName;
90
+ if (ownerHasWarned.hasOwnProperty(currentName)) {
91
+ return;
92
+ }
93
+ ownerHasWarned[currentName] = true;
94
+
95
+ var message = 'Each child in an array should have a unique "key" prop. ' +
96
+ 'Check the render method of ' + currentName + '.';
97
+ if (!component.isOwnedBy(ReactCurrentOwner.current)) {
98
+ // Name of the component that originally created this child.
99
+ var childOwnerName =
100
+ component.props[OWNER] && component.props[OWNER].constructor.displayName;
101
+
102
+ // Usually the current owner is the offender, but if it accepts
103
+ // children as a property, it may be the creator of the child that's
104
+ // responsible for assigning it a key.
105
+ message += ' It was passed a child from ' + childOwnerName + '.';
106
}
156
- return true;
107
+
108
+ global.console && console.warn && console.warn(message);
109
}
110
111
/**
160
- * Append children from the source array to the target array. Make sure all
161
- * children have an internal identity assigned to it based on insertion point.
112
+ * Ensure that every component either is passed in a static location or, if
113
+ * if it's passed in an array, has an explicit key property defined.
114
*
163
- * @param {number} Index of the current array grouping.
164
- * @param {array} Source array.
165
- * @param {array} Target array that will be appended to.
115
+ * @internal
116
+ * @param {*} component Statically passed child of any type.
117
+ * @return {boolean}
118
*/
167
-function appendNestedChildren(groupingIndex, sourceArray, targetArray) {
168
- for (var i = 0; i < sourceArray.length; i++) {
169
- var child = sourceArray[i];
170
- if (isEmptyChild(child)) {
171
- continue;
119
+function validateChildKeys(component) {
120
+ if (Array.isArray(component)) {
121
+ for (var i = 0; i < component.length; i++) {
122
+ var child = component[i];
123
+ if (ReactComponent.isValidComponent(child)) {
124
+ validateExplicitKey(child);
125
+ }
126
}
173
- assignKey(groupingIndex, child, i);
174
- // TODO: Invalid components like strings could possibly need
175
- // keys assigned to them here. Usually they're not stateful but
176
- // CSS transitions and special events could make them stateful.
177
- targetArray.push(child);
127
+ } else if (ReactComponent.isValidComponent(component)) {
128
+ // This component was passed in a valid location.
129
+ component[IS_KEY_VALIDATED] = true;
130
}
131
}
132
170
);
171
},
172
173
+ /**
174
+ * Generate a key string that identifies a component within a set.
175
+ *
176
+ * @param {*} component A component that could contain a manual key.
177
+ * @param {number} index Index that is used if a manual key is not provided.
178
+ * @return {string}
179
+ * @internal
180
+ */
181
+ getKey: function(component, index) {
182
+ if (component && component.props && component.props.key != null) {
183
+ // Explicit key
184
+ return '' + component.props.key;
185
+ }
186
+ // Implicit key determined by the index in the set
187
+ return '' + index;
188
+ },
189
+
190
/**
191
* @internal
192
*/
306
// All components start unmounted.
307
this._lifeCycleState = ComponentLifeCycle.UNMOUNTED;
308
340
- // Children can be either an array or more than one argument
341
- if (arguments.length < 2) {
342
- return;
343
- }
344
-
345
- if (arguments.length === 2) {
346
-
347
- // A single string or number child is treated as content, not an array.
348
- var type = typeof children;
349
- if (children == null || type === 'string' || type === 'number') {
350
- this.props.children = children;
351
- return;
352
- }
353
-
354
- // A single array can be reused if it's already flat
355
- if (Array.isArray(children) && tryToReuseArray(children)) {
356
- this.props.children = children;
357
- return;
309
+ // Children can be more than one argument
310
+ var childrenLength = arguments.length - 1;
311
+ if (childrenLength === 1) {
312
+ if (__DEV__) {
313
+ validateChildKeys(children);
314
}
359
-
360
- }
361
-
362
- // Subsequent arguments are rolled into one child array. Array arguments
363
- // are flattened onto it. This is inlined to avoid extra heap allocation.
364
- var targetArray = null;
365
- for (var i = 1; i < arguments.length; i++) {
366
- var child = arguments[i];
367
- if (Array.isArray(child)) {
368
- if (child.length === 0) {
369
- continue;
370
- }
371
-
372
- if (targetArray === null) {
373
- targetArray = [];
374
- }
375
- appendNestedChildren(i - 1, child, targetArray);
376
-
377
- } else if (!isEmptyChild(child)) {
378
-
379
- // Only truthy internal keys are valid. If it's not, we assign one.
380
- if (ReactComponent.isValidComponent(child) && !child._key) {
381
- // This is a static node and therefore safe to key by index.
382
- // No warning necessary.
383
- child._key = createKey(child.props.key, i - 1);
315
+ this.props.children = children;
316
+ } else if (childrenLength > 1) {
317
+ var childArray = Array(childrenLength);
318
+ for (var i = 0; i < childrenLength; i++) {
319
+ if (__DEV__) {
320
+ validateChildKeys(arguments[i + 1]);
321
}
385
-
386
- if (targetArray === null) {
387
- targetArray = [];
388
- }
389
- targetArray.push(child);
390
-
322
+ childArray[i] = arguments[i + 1];
323
}
324
+ this.props.children = childArray;
325
}
393
- this.props.children = targetArray;
326
},
327
328
/**