Skip to content

Commit fc96a52

Browse files
authored
Refactor React.Children to reduce indirection (facebook#18332)
* Don't pool traversal context * Remove traverseAllChildrenImpl indirection All usages are internal so we can simply use the inner function directly. * Implement forEach through map * Remove second usage of traverseAllChildren This isn't useful by itself but makes the layering easier to follow. traverseAllChildren is only used at the lowest layer now. * Reimplement count() and toArray() in terms of map() * Inline the only use of mapSingleChildIntoContext * Move forEach down in the file * Use the language Get rid of the traversal context. Use closures. * Make mapIntoArray take an already escaped prefix * Move count state out of mapIntoArray * Inline traverseAllChildren into mapIntoArray * Inline handleChild into mapIntoArray
1 parent fd61f7e commit fc96a52

File tree

1 file changed

+91
-180
lines changed

1 file changed

+91
-180
lines changed

packages/react/src/ReactChildren.js

Lines changed: 91 additions & 180 deletions
Original file line numberDiff line numberDiff line change
@@ -50,58 +50,29 @@ function escapeUserProvidedKey(text) {
5050
return ('' + text).replace(userProvidedKeyEscapeRegex, '$&/');
5151
}
5252

53-
const POOL_SIZE = 10;
54-
const traverseContextPool = [];
55-
function getPooledTraverseContext(
56-
mapResult,
57-
keyPrefix,
58-
mapFunction,
59-
mapContext,
60-
) {
61-
if (traverseContextPool.length) {
62-
const traverseContext = traverseContextPool.pop();
63-
traverseContext.result = mapResult;
64-
traverseContext.keyPrefix = keyPrefix;
65-
traverseContext.func = mapFunction;
66-
traverseContext.context = mapContext;
67-
traverseContext.count = 0;
68-
return traverseContext;
69-
} else {
70-
return {
71-
result: mapResult,
72-
keyPrefix: keyPrefix,
73-
func: mapFunction,
74-
context: mapContext,
75-
count: 0,
76-
};
77-
}
78-
}
79-
80-
function releaseTraverseContext(traverseContext) {
81-
traverseContext.result = null;
82-
traverseContext.keyPrefix = null;
83-
traverseContext.func = null;
84-
traverseContext.context = null;
85-
traverseContext.count = 0;
86-
if (traverseContextPool.length < POOL_SIZE) {
87-
traverseContextPool.push(traverseContext);
53+
/**
54+
* Generate a key string that identifies a component within a set.
55+
*
56+
* @param {*} component A component that could contain a manual key.
57+
* @param {number} index Index that is used if a manual key is not provided.
58+
* @return {string}
59+
*/
60+
function getComponentKey(component, index) {
61+
// Do some typechecking here since we call this blindly. We want to ensure
62+
// that we don't block potential future ES APIs.
63+
if (
64+
typeof component === 'object' &&
65+
component !== null &&
66+
component.key != null
67+
) {
68+
// Explicit key
69+
return escape(component.key);
8870
}
71+
// Implicit key determined by the index in the set
72+
return index.toString(36);
8973
}
9074

91-
/**
92-
* @param {?*} children Children tree container.
93-
* @param {!string} nameSoFar Name of the key path so far.
94-
* @param {!function} callback Callback to invoke with each child found.
95-
* @param {?*} traverseContext Used to pass information throughout the traversal
96-
* process.
97-
* @return {!number} The number of children in this subtree.
98-
*/
99-
function traverseAllChildrenImpl(
100-
children,
101-
nameSoFar,
102-
callback,
103-
traverseContext,
104-
) {
75+
function mapIntoArray(children, array, escapedPrefix, nameSoFar, callback) {
10576
const type = typeof children;
10677

10778
if (type === 'undefined' || type === 'boolean') {
@@ -129,13 +100,33 @@ function traverseAllChildrenImpl(
129100
}
130101

131102
if (invokeCallback) {
132-
callback(
133-
traverseContext,
134-
children,
135-
// If it's the only child, treat the name as if it was wrapped in an array
136-
// so that it's consistent if the number of children grows.
137-
nameSoFar === '' ? SEPARATOR + getComponentKey(children, 0) : nameSoFar,
138-
);
103+
const child = children;
104+
let mappedChild = callback(child);
105+
// If it's the only child, treat the name as if it was wrapped in an array
106+
// so that it's consistent if the number of children grows:
107+
let childKey =
108+
nameSoFar === '' ? SEPARATOR + getComponentKey(child, 0) : nameSoFar;
109+
if (Array.isArray(mappedChild)) {
110+
let escapedChildKey = '';
111+
if (childKey != null) {
112+
escapedChildKey = escapeUserProvidedKey(childKey) + '/';
113+
}
114+
mapIntoArray(mappedChild, array, escapedChildKey, c => c);
115+
} else if (mappedChild != null) {
116+
if (isValidElement(mappedChild)) {
117+
mappedChild = cloneAndReplaceKey(
118+
mappedChild,
119+
// Keep both the (mapped) and old keys if they differ, just as
120+
// traverseAllChildren used to do for objects as children
121+
escapedPrefix +
122+
(mappedChild.key && (!child || child.key !== mappedChild.key)
123+
? escapeUserProvidedKey(mappedChild.key) + '/'
124+
: '') +
125+
childKey,
126+
);
127+
}
128+
array.push(mappedChild);
129+
}
139130
return 1;
140131
}
141132

@@ -149,11 +140,12 @@ function traverseAllChildrenImpl(
149140
for (let i = 0; i < children.length; i++) {
150141
child = children[i];
151142
nextName = nextNamePrefix + getComponentKey(child, i);
152-
subtreeCount += traverseAllChildrenImpl(
143+
subtreeCount += mapIntoArray(
153144
child,
145+
array,
146+
escapedPrefix,
154147
nextName,
155148
callback,
156-
traverseContext,
157149
);
158150
}
159151
} else {
@@ -188,11 +180,12 @@ function traverseAllChildrenImpl(
188180
while (!(step = iterator.next()).done) {
189181
child = step.value;
190182
nextName = nextNamePrefix + getComponentKey(child, ii++);
191-
subtreeCount += traverseAllChildrenImpl(
183+
subtreeCount += mapIntoArray(
192184
child,
185+
array,
186+
escapedPrefix,
193187
nextName,
194188
callback,
195-
traverseContext,
196189
);
197190
}
198191
} else if (type === 'object') {
@@ -218,121 +211,6 @@ function traverseAllChildrenImpl(
218211
return subtreeCount;
219212
}
220213

221-
/**
222-
* Traverses children that are typically specified as `props.children`, but
223-
* might also be specified through attributes:
224-
*
225-
* - `traverseAllChildren(this.props.children, ...)`
226-
* - `traverseAllChildren(this.props.leftPanelChildren, ...)`
227-
*
228-
* The `traverseContext` is an optional argument that is passed through the
229-
* entire traversal. It can be used to store accumulations or anything else that
230-
* the callback might find relevant.
231-
*
232-
* @param {?*} children Children tree object.
233-
* @param {!function} callback To invoke upon traversing each child.
234-
* @param {?*} traverseContext Context for traversal.
235-
* @return {!number} The number of children in this subtree.
236-
*/
237-
function traverseAllChildren(children, callback, traverseContext) {
238-
if (children == null) {
239-
return 0;
240-
}
241-
242-
return traverseAllChildrenImpl(children, '', callback, traverseContext);
243-
}
244-
245-
/**
246-
* Generate a key string that identifies a component within a set.
247-
*
248-
* @param {*} component A component that could contain a manual key.
249-
* @param {number} index Index that is used if a manual key is not provided.
250-
* @return {string}
251-
*/
252-
function getComponentKey(component, index) {
253-
// Do some typechecking here since we call this blindly. We want to ensure
254-
// that we don't block potential future ES APIs.
255-
if (
256-
typeof component === 'object' &&
257-
component !== null &&
258-
component.key != null
259-
) {
260-
// Explicit key
261-
return escape(component.key);
262-
}
263-
// Implicit key determined by the index in the set
264-
return index.toString(36);
265-
}
266-
267-
function forEachSingleChild(bookKeeping, child, name) {
268-
const {func, context} = bookKeeping;
269-
func.call(context, child, bookKeeping.count++);
270-
}
271-
272-
/**
273-
* Iterates through children that are typically specified as `props.children`.
274-
*
275-
* See https://reactjs.org/docs/react-api.html#reactchildrenforeach
276-
*
277-
* The provided forEachFunc(child, index) will be called for each
278-
* leaf child.
279-
*
280-
* @param {?*} children Children tree container.
281-
* @param {function(*, int)} forEachFunc
282-
* @param {*} forEachContext Context for forEachContext.
283-
*/
284-
function forEachChildren(children, forEachFunc, forEachContext) {
285-
if (children == null) {
286-
return children;
287-
}
288-
const traverseContext = getPooledTraverseContext(
289-
null,
290-
null,
291-
forEachFunc,
292-
forEachContext,
293-
);
294-
traverseAllChildren(children, forEachSingleChild, traverseContext);
295-
releaseTraverseContext(traverseContext);
296-
}
297-
298-
function mapSingleChildIntoContext(bookKeeping, child, childKey) {
299-
const {result, keyPrefix, func, context} = bookKeeping;
300-
301-
let mappedChild = func.call(context, child, bookKeeping.count++);
302-
if (Array.isArray(mappedChild)) {
303-
mapIntoWithKeyPrefixInternal(mappedChild, result, childKey, c => c);
304-
} else if (mappedChild != null) {
305-
if (isValidElement(mappedChild)) {
306-
mappedChild = cloneAndReplaceKey(
307-
mappedChild,
308-
// Keep both the (mapped) and old keys if they differ, just as
309-
// traverseAllChildren used to do for objects as children
310-
keyPrefix +
311-
(mappedChild.key && (!child || child.key !== mappedChild.key)
312-
? escapeUserProvidedKey(mappedChild.key) + '/'
313-
: '') +
314-
childKey,
315-
);
316-
}
317-
result.push(mappedChild);
318-
}
319-
}
320-
321-
function mapIntoWithKeyPrefixInternal(children, array, prefix, func, context) {
322-
let escapedPrefix = '';
323-
if (prefix != null) {
324-
escapedPrefix = escapeUserProvidedKey(prefix) + '/';
325-
}
326-
const traverseContext = getPooledTraverseContext(
327-
array,
328-
escapedPrefix,
329-
func,
330-
context,
331-
);
332-
traverseAllChildren(children, mapSingleChildIntoContext, traverseContext);
333-
releaseTraverseContext(traverseContext);
334-
}
335-
336214
/**
337215
* Maps children that are typically specified as `props.children`.
338216
*
@@ -351,7 +229,17 @@ function mapChildren(children, func, context) {
351229
return children;
352230
}
353231
const result = [];
354-
mapIntoWithKeyPrefixInternal(children, result, null, func, context);
232+
let count = 0;
233+
mapIntoArray(
234+
children,
235+
result,
236+
'',
237+
'',
238+
function(child) {
239+
return func.call(context, child, count++);
240+
},
241+
context,
242+
);
355243
return result;
356244
}
357245

@@ -365,7 +253,32 @@ function mapChildren(children, func, context) {
365253
* @return {number} The number of children.
366254
*/
367255
function countChildren(children) {
368-
return traverseAllChildren(children, () => null, null);
256+
let n = 0;
257+
mapChildren(children, () => n++);
258+
return n;
259+
}
260+
261+
/**
262+
* Iterates through children that are typically specified as `props.children`.
263+
*
264+
* See https://reactjs.org/docs/react-api.html#reactchildrenforeach
265+
*
266+
* The provided forEachFunc(child, index) will be called for each
267+
* leaf child.
268+
*
269+
* @param {?*} children Children tree container.
270+
* @param {function(*, int)} forEachFunc
271+
* @param {*} forEachContext Context for forEachContext.
272+
*/
273+
function forEachChildren(children, forEachFunc, forEachContext) {
274+
mapChildren(
275+
children,
276+
function() {
277+
forEachFunc.apply(this, arguments);
278+
// Don't return anything.
279+
},
280+
forEachContext,
281+
);
369282
}
370283

371284
/**
@@ -375,9 +288,7 @@ function countChildren(children) {
375288
* See https://reactjs.org/docs/react-api.html#reactchildrentoarray
376289
*/
377290
function toArray(children) {
378-
const result = [];
379-
mapIntoWithKeyPrefixInternal(children, result, null, child => child);
380-
return result;
291+
return mapChildren(children, child => child) || [];
381292
}
382293

383294
/**

0 commit comments

Comments
 (0)