Skip to content

Commit ea85854

Browse files
committed
Avoid allocating arrays for single children
1 parent d598771 commit ea85854

2 files changed

Lines changed: 23 additions & 14 deletions

File tree

src/diff/children.js

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@ import { getDomSibling } from '../component';
2323
* Diff the children of a virtual node
2424
* @param {PreactElement} parentDom The DOM element whose children are being
2525
* diffed
26-
* @param {ComponentChildren[]} renderResult
26+
* @param {ComponentChildren|ComponentChildren[]} renderResult A single child or
27+
* an array of them; single children are not wrapped by the caller.
2728
* @param {VNode} newParentVNode The new virtual node whose children should be
2829
* diff'ed against oldParentVNode
2930
* @param {VNode} oldParentVNode The old virtual node whose children should be
@@ -71,11 +72,16 @@ export function diffChildren(
7172
/** @type {VNode[]} */
7273
let oldChildren = oldParentVNode._children || EMPTY_ARR;
7374

74-
let newChildrenLength = renderResult.length;
75+
// `renderResult` is passed unwrapped: the overwhelming majority of children
76+
// diffs are a single child (~84% in the wild, see #2618), so wrapping it in
77+
// a throwaway `[child]` array at the call site is pure allocation churn.
78+
let isArr = isArray(renderResult);
79+
let newChildrenLength = isArr ? renderResult.length : 1;
7580

7681
oldDom = constructNewChildrenArray(
7782
newParentVNode,
7883
renderResult,
84+
isArr,
7985
oldChildren,
8086
oldDom,
8187
newChildrenLength
@@ -158,12 +164,14 @@ export function diffChildren(
158164

159165
/**
160166
* @param {VNode} newParentVNode
161-
* @param {ComponentChildren[]} renderResult
167+
* @param {ComponentChildren|ComponentChildren[]} renderResult
168+
* @param {boolean} isArr Whether `renderResult` is an array of children
162169
* @param {VNode[]} oldChildren
163170
*/
164171
function constructNewChildrenArray(
165172
newParentVNode,
166173
renderResult,
174+
isArr,
167175
oldChildren,
168176
oldDom,
169177
newChildrenLength
@@ -184,18 +192,19 @@ function constructNewChildrenArray(
184192
* real reorder happened and we need to compute the minimal set of moves. */
185193
let moved = false;
186194

187-
newParentVNode._children = new Array(newChildrenLength);
195+
/** @type {VNode[]} */
196+
let newChildren = (newParentVNode._children = new Array(newChildrenLength));
188197
for (i = 0; i < newChildrenLength; i++) {
189198
// @ts-expect-error We are reusing the childVNode variable to hold both the
190199
// pre and post normalized childVNode
191-
childVNode = renderResult[i];
200+
childVNode = isArr ? renderResult[i] : renderResult;
192201

193202
if (
194203
childVNode == NULL ||
195204
typeof childVNode == 'boolean' ||
196205
typeof childVNode == 'function'
197206
) {
198-
newParentVNode._children[i] = NULL;
207+
newChildren[i] = NULL;
199208
continue;
200209
}
201210
// If this newVNode is being reused (e.g. <div>{reuse}{reuse}</div>) in the same diff,
@@ -208,15 +217,15 @@ function constructNewChildrenArray(
208217
typeof childVNode != 'object' ||
209218
childVNode.constructor == String
210219
) {
211-
childVNode = newParentVNode._children[i] = createVNode(
220+
childVNode = newChildren[i] = createVNode(
212221
NULL,
213222
childVNode,
214223
NULL,
215224
NULL,
216225
NULL
217226
);
218227
} else if (isArray(childVNode)) {
219-
childVNode = newParentVNode._children[i] = createVNode(
228+
childVNode = newChildren[i] = createVNode(
220229
Fragment,
221230
{ children: childVNode },
222231
NULL,
@@ -228,15 +237,15 @@ function constructNewChildrenArray(
228237
// scenario:
229238
// const reuse = <div />
230239
// <div>{reuse}<span />{reuse}</div>
231-
childVNode = newParentVNode._children[i] = createVNode(
240+
childVNode = newChildren[i] = createVNode(
232241
childVNode.type,
233242
childVNode.props,
234243
childVNode.key,
235244
childVNode.ref ? childVNode.ref : NULL,
236245
childVNode._original
237246
);
238247
} else {
239-
newParentVNode._children[i] = childVNode;
248+
newChildren[i] = childVNode;
240249
}
241250

242251
const skewedIndex = i + skew;
@@ -331,7 +340,7 @@ function constructNewChildrenArray(
331340
/** @type {number[]} length of the longest increasing subsequence ending at child i */
332341
let lisLengths = [];
333342
for (i = 0; i < newChildrenLength; i++) {
334-
childVNode = newParentVNode._children[i];
343+
childVNode = newChildren[i];
335344
if (childVNode && childVNode._flags & MATCHED) {
336345
// Binary search for the insertion point, keeping the pass at
337346
// O(n log n) even for pathological reorders.
@@ -359,7 +368,7 @@ function constructNewChildrenArray(
359368
if (lisLengths[i] == skew) {
360369
skew--;
361370
} else {
362-
newParentVNode._children[i]._flags |= INSERT_VNODE;
371+
newChildren[i]._flags |= INSERT_VNODE;
363372
}
364373
}
365374
}

src/diff/index.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -329,7 +329,7 @@ export function diff(
329329

330330
oldDom = diffChildren(
331331
parentDom,
332-
isArray(renderResult) ? renderResult : [renderResult],
332+
renderResult,
333333
newVNode,
334334
oldVNode,
335335
globalContext,
@@ -670,7 +670,7 @@ function diffElementNodes(
670670

671671
diffChildren(
672672
parentDom,
673-
isArray(newChildren) ? newChildren : [newChildren],
673+
newChildren,
674674
newVNode,
675675
oldVNode,
676676
globalContext,

0 commit comments

Comments
 (0)