@samitouri / QOS-React-2 / commits / 007b75f78a
Fork 0

The library for web and native user interfaces.

Flatten Children A Single Level

This expects static children as additional arguments to the constructor and flattens any array arguments one level deep. Component(props, child1, child2, arrayOfChildren, child3) -> .props.children = [child1, child2, ...arrayOfChildren, child3] This can avoid an additional heap allocation for the unflat array. It allows you to pass nested arrays and objects like you used to. Those aren't immediately flattened. That makes this a fairly safe change. Passing a dynamic array without key properties will yield a warning (once). Might consider throwing later. Once we change the transpiler to use the new syntax, you'll end up with a single flat array in normal usage. This doesn't actually update the JSX transform.

CommitSyncScript committed Jun 4, 2013 at 12:15 UTC 007b75f78a1818a020523acb540c381e4d588f60
5 files changed +135 -19
src/core/ReactComponent.js
+102 -3
@@ -50,6 +50,60 @@ var ComponentLifeCycle = keyMirror({
50 UNMOUNTED: null
51 });
52
53 +/**
54 + * Warn if there's no key explicitly set on dynamic arrays of children.
55 + * This allows us to keep track of children between updates.
56 + */
57 +
58 +var CHILD_HAS_NO_IDENTITY =
59 + 'You are passing a dynamic array of children. You should set the ' +
60 + 'property "key" to a string that uniquely identifies each child.';
61 +
62 +var HAS_WARNED = false;
63 +
64 +/**
65 + * Helpers for flattening child arguments onto a new array or use an existing
66 + * one.
67 + */
68 +
69 +function isEmptyChild(child) {
70 + return child == null || typeof child === 'boolean';
71 +}
72 +
73 +function assignKey(setKey, child, index) {
74 + if (ReactComponent.isValidComponent(child)) {
75 + var key = child._key || child.props.key;
76 + if (__DEV__) {
77 + if (!HAS_WARNED && !key) {
78 + HAS_WARNED = true;
79 + console && console.warn && console.warn(CHILD_HAS_NO_IDENTITY);
80 + }
81 + }
82 + child._key = (setKey ? setKey + ':' : '') + (key || ('' + index));
83 + }
84 +}
85 +
86 +function tryToReuseArray(children) {
87 + for (var i = 0; i < children.length; i++) {
88 + var child = children[i];
89 + if (isEmptyChild(child)) return false;
90 + assignKey('', child, i);
91 + }
92 + return true;
93 +}
94 +
95 +function appendNestedChildren(parentKey, sourceArray, targetArray) {
96 + for (var i = 0; i < sourceArray.length; i++) {
97 + var child = sourceArray[i];
98 + if (isEmptyChild(child)) continue;
99 + assignKey(parentKey, child, i);
100 + // TODO: Invalid components like strings could possibly need
101 + // keys assigned to them here. Usually they're not stateful but
102 + // CSS transitions and special events could make them stateful.
103 + targetArray.push(child);
104 + }
105 +}
106 +
107 /**
108 * Components are the basic units of composition in React.
109 *
@@ -206,13 +260,58 @@ var ReactComponent = {
260 */
261 construct: function(initialProps, children) {
262 this.props = initialProps || {};
209 - if (typeof children !== 'undefined') {
210 - this.props.children = children;
211 - }
263 // Record the component responsible for creating this component.
264 this.props[OWNER] = ReactCurrentOwner.current;
265 // All components start unmounted.
266 this._lifeCycleState = ComponentLifeCycle.UNMOUNTED;
267 +
268 + // Children can be either an array or more than one argument
269 + if (arguments.length < 2) {
270 + return;
271 + }
272 +
273 + if (arguments.length === 2) {
274 +
275 + // A single string or number child is treated as content, not an array.
276 + var type = typeof children;
277 + if (children == null || type === 'string' || type === 'number') {
278 + this.props.children = children;
279 + return;
280 + }
281 +
282 + // A single array can be reused if it's already flat
283 + if (Array.isArray(children) && tryToReuseArray(children)) {
284 + this.props.children = children;
285 + return;
286 + }
287 +
288 + }
289 +
290 + // Subsequent arguments are rolled into one child array. Array arguments
291 + // are flattened onto it. This is inlined to avoid extra heap allocation.
292 + var targetArray = null;
293 + for (var i = 1; i < arguments.length; i++) {
294 + var child = arguments[i];
295 + if (Array.isArray(child)) {
296 + if (child.length === 0) continue;
297 +
298 + if (targetArray === null) targetArray = [];
299 + appendNestedChildren(i - 1, child, targetArray);
300 +
301 + } else if (!isEmptyChild(child)) {
302 +
303 + if (ReactComponent.isValidComponent(child) && !child._key) {
304 + // This is a static node and therefore safe to key by index.
305 + // No warning necessary.
306 + child._key = child.props.key || ('' + (i - 1));
307 + }
308 +
309 + if (targetArray === null) targetArray = [];
310 + targetArray.push(child);
311 +
312 + }
313 + }
314 + this.props.children = targetArray;
315 },
316
317 /**
src/core/ReactCompositeComponent.js
+6 -5
@@ -396,7 +396,8 @@ var ReactCompositeComponentMixin = {
396 * @internal
397 */
398 construct: function(initialProps, children) {
399 - ReactComponent.Mixin.construct.call(this, initialProps, children);
399 + // Children can be either an array or more than one argument
400 + ReactComponent.Mixin.construct.apply(this, arguments);
401 this.state = null;
402 this._pendingState = null;
403 this._compositeLifeCycleState = null;
@@ -784,9 +785,7 @@ var ReactCompositeComponent = {
785 * @public
786 */
787 createClass: function(spec) {
787 - var Constructor = function(initialProps, children) {
788 - this.construct(initialProps, children);
789 - };
788 + var Constructor = function() {};
789 Constructor.prototype = new ReactCompositeComponentBase();
790 Constructor.prototype.constructor = Constructor;
791 mixSpecIntoComponent(Constructor, spec);
@@ -796,7 +795,9 @@ var ReactCompositeComponent = {
795 );
796
797 var ConvenienceConstructor = function(props, children) {
799 - return new Constructor(props, children);
798 + var instance = new Constructor();
799 + instance.construct.apply(instance, arguments);
800 + return instance;
801 };
802 ConvenienceConstructor.componentConstructor = Constructor;
803 ConvenienceConstructor.originalSpec = spec;
src/core/ReactDOM.js
+4 -4
@@ -40,15 +40,15 @@ var objMapKeyVal = require('objMapKeyVal');
40 * @private
41 */
42 function createDOMComponentClass(tag, omitClose) {
43 - var Constructor = function(initialProps, children) {
44 - this.construct(initialProps, children);
45 - };
43 + var Constructor = function() {};
44
45 Constructor.prototype = new ReactNativeComponent(tag, omitClose);
46 Constructor.prototype.constructor = Constructor;
47
48 return function(props, children) {
51 - return new Constructor(props, children);
49 + var instance = new Constructor();
50 + instance.construct.apply(instance, arguments);
51 + return instance;
52 };
53 }
54
src/core/__tests__/ReactMultiChildReconcile-test.js
+3 -4
@@ -41,12 +41,11 @@ var stripEmptyValues = function(obj) {
41 };
42
43 /**
44 - * Children are currently named '{<providedKey>}' so we can retrieve the
45 - * originally provided key by stripping out the braces. This relies on a tiny
46 - * implementation detail of the rendering system.
44 + * These children are wrapped in an array and therefore their keys are prefixed.
45 + * This relies on a tiny implementation detail of the rendering system.
46 */
47 var getOriginalKey = function(childName) {
49 - return childName.substr(1, childName.length - 2);
48 + return childName.substr(3);
49 };
50
51 /**
src/utils/flattenChildren.js
+20 -3
@@ -37,15 +37,28 @@ if (__DEV__) {
37 'as a child of a React component.';
38 }
39
40 +var DUPLICATE_KEY_ERROR =
41 + 'You have two children with identical keys. Make sure that you set the ' +
42 + '"key" property to a unique value such as a row ID.';
43 +
44 /**
45 * If there is only a single child, it still needs a name.
46 */
47 var ONLY_CHILD_NAME = '0';
48
49 var flattenChildrenImpl = function(res, children, nameSoFar) {
50 + var key, escapedKey;
51 if (Array.isArray(children)) {
52 for (var i = 0; i < children.length; i++) {
48 - flattenChildrenImpl(res, children[i], nameSoFar + '[' + i + ']');
53 + var child = children[i];
54 + key = child && child.mountInContainerNode &&
55 + (child._key || child.props.key);
56 + escapedKey = key ? escapeTextForBrowser(key) : ('' + i);
57 + flattenChildrenImpl(
58 + res,
59 + child,
60 + nameSoFar + ':' + escapedKey
61 + );
62 }
63 } else {
64 var type = typeof children;
@@ -55,16 +68,20 @@ var flattenChildrenImpl = function(res, children, nameSoFar) {
68 res[storageName] = null;
69 } else if (children.mountComponentIntoNode) {
70 /* We found a component instance */
71 + if (__DEV__) {
72 + throwIf(res.hasOwnProperty(storageName), DUPLICATE_KEY_ERROR);
73 + }
74 res[storageName] = children;
75 } else {
76 if (type === 'object') {
77 throwIf(children && children.nodeType === 1, INVALID_CHILD);
62 - for (var key in children) {
78 + for (key in children) {
79 if (children.hasOwnProperty(key)) {
80 + escapedKey = escapeTextForBrowser(key);
81 flattenChildrenImpl(
82 res,
83 children[key],
67 - nameSoFar + '{' + escapeTextForBrowser(key) + '}'
84 + nameSoFar + ':' + escapedKey
85 );
86 }
87 }