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

Refactor React.Children to reduce indirection (#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

Dan Abramov committed Mar 23, 2020 at 22:56 UTC fc96a52be36fc3ac7dd839df3f54e1df7baf7f06
1 file changed +91 -180
packages/react/src/ReactChildren.js
+91 -180
@@ -50,58 +50,29 @@ function escapeUserProvidedKey(text) {
50 return ('' + text).replace(userProvidedKeyEscapeRegex, '$&/');
51 }
52
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);
70 }
71 + // Implicit key determined by the index in the set
72 + return index.toString(36);
73 }
74
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) {
76 const type = typeof children;
77
78 if (type === 'undefined' || type === 'boolean') {
@@ -129,13 +100,33 @@ function traverseAllChildrenImpl(
100 }
101
102 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 + }
130 return 1;
131 }
132
@@ -149,11 +140,12 @@ function traverseAllChildrenImpl(
140 for (let i = 0; i < children.length; i++) {
141 child = children[i];
142 nextName = nextNamePrefix + getComponentKey(child, i);
152 - subtreeCount += traverseAllChildrenImpl(
143 + subtreeCount += mapIntoArray(
144 child,
145 + array,
146 + escapedPrefix,
147 nextName,
148 callback,
156 - traverseContext,
149 );
150 }
151 } else {
@@ -188,11 +180,12 @@ function traverseAllChildrenImpl(
180 while (!(step = iterator.next()).done) {
181 child = step.value;
182 nextName = nextNamePrefix + getComponentKey(child, ii++);
191 - subtreeCount += traverseAllChildrenImpl(
183 + subtreeCount += mapIntoArray(
184 child,
185 + array,
186 + escapedPrefix,
187 nextName,
188 callback,
195 - traverseContext,
189 );
190 }
191 } else if (type === 'object') {
@@ -218,121 +211,6 @@ function traverseAllChildrenImpl(
211 return subtreeCount;
212 }
213
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 -
214 /**
215 * Maps children that are typically specified as `props.children`.
216 *
@@ -351,7 +229,17 @@ function mapChildren(children, func, context) {
229 return children;
230 }
231 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 + );
243 return result;
244 }
245
@@ -365,7 +253,32 @@ function mapChildren(children, func, context) {
253 * @return {number} The number of children.
254 */
255 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 + );
282 }
283
284 /**
@@ -375,9 +288,7 @@ function countChildren(children) {
288 * See https://reactjs.org/docs/react-api.html#reactchildrentoarray
289 */
290 function toArray(children) {
378 - const result = [];
379 - mapIntoWithKeyPrefixInternal(children, result, null, child => child);
380 - return result;
291 + return mapChildren(children, child => child) || [];
292 }
293
294 /**