@samitouri / QOS-React-2 / commits / f4321f8624
Fork 0

The library for web and native user interfaces.

[React Children] Step One: Refactor flattenChildren

Refactoring `flattenChildren` so that the traversal code is reusable for other purposes.

Jordan Walke committed Jul 16, 2013 at 11:29 UTC f4321f862485032f1f41cf2011475e57d53834f7
2 files changed +160 -65
src/utils/flattenChildren.js
+16 -65
@@ -18,85 +18,36 @@
18
19 "use strict";
20
21 -var ReactComponent = require('ReactComponent');
22 -var ReactTextComponent = require('ReactTextComponent');
23 -
21 var throwIf = require('throwIf');
22 +var traverseAllChildren = require('traverseAllChildren');
23
24 /**
27 - * @polyFill Array.isArray
25 + * @param {function} traverseContext Context passed through traversal.
26 + * @param {?ReactComponent} child React child component.
27 + * @param {!string} name String name of key path to child.
28 */
29 -
30 -
31 -var INVALID_CHILD = 'INVALID_CHILD';
32 -if (__DEV__) {
33 - INVALID_CHILD =
34 - 'You may not pass a child of that type to a React component. It ' +
35 - 'is a common mistake to try to pass a standard browser DOM element ' +
36 - 'as a child of a React component.';
37 -}
38 -
39 -var DUPLICATE_KEY_ERROR =
40 - 'You have two children with identical keys. Make sure that you set the ' +
41 - '"key" property to a unique value such as a row ID.';
42 -
43 -var flattenChildrenImpl = function(res, children, nameSoFar) {
44 - if (Array.isArray(children)) {
45 - for (var i = 0; i < children.length; i++) {
46 - var child = children[i];
47 - flattenChildrenImpl(
48 - res,
49 - child,
50 - nameSoFar + '[' + ReactComponent.getKey(child, i) + ']'
51 - );
52 - }
53 - } else {
54 - var type = typeof children;
55 - var isOnlyChild = nameSoFar === '';
56 - // If it's the only child, treat the name as if it was wrapped in an array
57 - // so that it's consistent if the number of children grows
58 - var storageName = isOnlyChild ?
59 - '[' + ReactComponent.getKey(children, 0) + ']' :
60 - nameSoFar;
61 - if (children === null || children === undefined || type === 'boolean') {
62 - res[storageName] = null;
63 - } else if (children.mountComponentIntoNode) {
64 - /* We found a component instance */
65 - if (__DEV__) {
66 - throwIf(res.hasOwnProperty(storageName), DUPLICATE_KEY_ERROR);
67 - }
68 - res[storageName] = children;
69 - } else {
70 - if (type === 'object') {
71 - throwIf(children && children.nodeType === 1, INVALID_CHILD);
72 - for (var key in children) {
73 - if (children.hasOwnProperty(key)) {
74 - flattenChildrenImpl(
75 - res,
76 - children[key],
77 - nameSoFar + '{' + key + '}'
78 - );
79 - }
80 - }
81 - } else if (type === 'string') {
82 - res[storageName] = new ReactTextComponent(children);
83 - } else if (type === 'number') {
84 - res[storageName] = new ReactTextComponent('' + children);
85 - }
86 - }
29 +function flattenSingleChildIntoContext(traverseContext, child, name) {
30 + // We found a component instance
31 + var result = traverseContext;
32 + if (__DEV__) {
33 + throwIf(
34 + result.hasOwnProperty(name),
35 + traverseAllChildren.DUPLICATE_KEY_ERROR
36 + );
37 }
88 -};
38 + result[name] = child;
39 +}
40
41 /**
42 * Flattens children that are typically specified as `props.children`.
92 - * @return {!Object} flattened children keyed by name.
43 + * @return {!object} flattened children keyed by name.
44 */
45 function flattenChildren(children) {
46 if (children === null || children === undefined) {
47 return children;
48 }
49 var result = {};
99 - flattenChildrenImpl(result, children, '');
50 + traverseAllChildren(children, flattenSingleChildIntoContext, result);
51 return result;
52 }
53
src/utils/traverseAllChildren.js new
+144
@@ -0,0 +1,144 @@
1 +/**
2 + * Copyright 2013 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 traverseAllChildren
17 + */
18 +
19 +"use strict";
20 +
21 +var ReactComponent = require('ReactComponent');
22 +var ReactTextComponent = require('ReactTextComponent');
23 +
24 +var throwIf = require('throwIf');
25 +
26 +/**
27 + * @polyFill Array.isArray
28 + */
29 +var DUPLICATE_KEY_ERROR = 'DUPLICATE_KEY_ERROR';
30 +var INVALID_CHILD = 'INVALID_CHILD';
31 +if (__DEV__) {
32 + INVALID_CHILD =
33 + 'You may not pass a child of that type to a React component. It ' +
34 + 'is a common mistake to try to pass a standard browser DOM element ' +
35 + 'as a child of a React component.';
36 + DUPLICATE_KEY_ERROR =
37 + 'You have two children with identical keys. Make sure that you set the ' +
38 + '"key" property to a unique value such as a row ID.';
39 +}
40 +
41 +/**
42 + * TODO: Test that:
43 + * 1. `mapChildren` transforms strings and numbers into `ReactTextComponent`.
44 + * 2. it('should fail when supplied duplicate key', function() {
45 + * 3. That a single child and an array with one item have the same key pattern.
46 + * });
47 + */
48 +
49 +/**
50 + * @param {?*} children Children tree container.
51 + * @param {!string} nameSoFar Name of the key path so far.
52 + * @param {!number} indexSoFar Number of children encountered until this point.
53 + * @param {!function} callback Callback to invoke with each child found.
54 + * @param {?*} traverseContext Used to pass information throughout the traversal
55 + * process.
56 + * @return {!number} The number of children in this subtree.
57 + */
58 +var traverseAllChildrenImpl =
59 + function(children, nameSoFar, indexSoFar, callback, traverseContext) {
60 + var subtreeCount = 0; // Count of children found in the current subtree.
61 + if (Array.isArray(children)) {
62 + for (var i = 0; i < children.length; i++) {
63 + var child = children[i];
64 + var nextName = nameSoFar + '[' + ReactComponent.getKey(child, i) + ']';
65 + var nextIndex = indexSoFar + subtreeCount;
66 + subtreeCount += traverseAllChildrenImpl(
67 + child,
68 + nextName,
69 + nextIndex,
70 + callback,
71 + traverseContext
72 + );
73 + }
74 + } else {
75 + var type = typeof children;
76 + var isOnlyChild = nameSoFar === '';
77 + // If it's the only child, treat the name as if it was wrapped in an array
78 + // so that it's consistent if the number of children grows
79 + var storageName = isOnlyChild ?
80 + '[' + ReactComponent.getKey(children, 0) + ']' :
81 + nameSoFar;
82 + if (children === null || children === undefined || type === 'boolean') {
83 + // All of the above are perceived as null.
84 + callback(traverseContext, null, storageName, indexSoFar);
85 + subtreeCount = 1;
86 + } else if (children.mountComponentIntoNode) {
87 + callback(traverseContext, children, storageName, indexSoFar);
88 + subtreeCount = 1;
89 + } else {
90 + if (type === 'object') {
91 + throwIf(children && children.nodeType === 1, INVALID_CHILD);
92 + for (var key in children) {
93 + if (children.hasOwnProperty(key)) {
94 + subtreeCount += traverseAllChildrenImpl(
95 + children[key],
96 + nameSoFar + '{' + key + '}',
97 + indexSoFar + subtreeCount,
98 + callback,
99 + traverseContext
100 + );
101 + }
102 + }
103 + } else if (type === 'string') {
104 + var normalizedText = new ReactTextComponent(children);
105 + callback(traverseContext, normalizedText, storageName, indexSoFar);
106 + subtreeCount += 1;
107 + } else if (type === 'number') {
108 + var normalizedNumber = new ReactTextComponent('' + children);
109 + callback(traverseContext, normalizedNumber, storageName, indexSoFar);
110 + subtreeCount += 1;
111 + }
112 + }
113 + }
114 + return subtreeCount;
115 + };
116 +
117 +/**
118 + * Traverses children that are typically specified as `props.children`, but
119 + * might also be specified through attributes:
120 + *
121 + * - `traverseAllChildren(this.props.children, ...)`
122 + * - `traverseAllChildren(this.props.leftPanelChildren, ...)`
123 + *
124 + * The `traverseContext` is an optional argument that is passed through the
125 + * entire traversal. It can be used to store accumulations or anything else that
126 + * the callback might find relevant.
127 + *
128 + * @param {?*} children Children tree object.
129 + * @param {!function} callback To invoke upon traversing each child.
130 + * @param {?*} traverseContext Context for traversal.
131 + */
132 +function traverseAllChildren(children, callback, traverseContext) {
133 + if (children !== null && children !== undefined) {
134 + traverseAllChildrenImpl(children, '', 0, callback, traverseContext);
135 + }
136 +}
137 +
138 +/**
139 + * Export the error code for use in other walking/mapping code.
140 + */
141 +traverseAllChildren.DUPLICATE_KEY_ERROR = DUPLICATE_KEY_ERROR;
142 +
143 +
144 +module.exports = traverseAllChildren;