Skip to content

Commit 00fc7ad

Browse files
committed
perf: use replace children for truncating nodes
1 parent d368215 commit 00fc7ad

File tree

3 files changed

+97
-6
lines changed

3 files changed

+97
-6
lines changed

packages/qwik/src/core/client/vnode-diff.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ import {
3232
dangerouslySetInnerHTML,
3333
} from '../shared/utils/markers';
3434
import { isPromise } from '../shared/utils/promises';
35-
import { type ValueOrPromise } from '../shared/utils/types';
35+
import { isArray, type ValueOrPromise } from '../shared/utils/types';
3636
import {
3737
getEventNameFromJsxEvent,
3838
getEventNameScopeFromJsxEvent,
@@ -308,7 +308,7 @@ export const vnode_diff = (
308308
* is an array produced by the `map` function.
309309
*/
310310
function descend(children: JSXChildren, descendVNode: boolean) {
311-
if (children == null) {
311+
if (children == null || (descendVNode && isArray(children) && children.length === 0)) {
312312
expectNoChildren();
313313
return;
314314
}

packages/qwik/src/core/client/vnode.ts

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -183,8 +183,9 @@ export const enum VNodeJournalOpCode {
183183
SetText = 1, // ------ [SetAttribute, target, text]
184184
SetAttribute = 2, // - [SetAttribute, target, ...(key, values)]]
185185
HoistStyles = 3, // -- [HoistStyles, document]
186-
Remove = 4, // ------- [Insert, target(parent), ...nodes]
187-
Insert = 5, // ------- [Insert, target(parent), reference, ...nodes]
186+
Remove = 4, // ------- [Remove, target(parent), ...nodes]
187+
RemoveAll = 5, // ------- [RemoveAll, target(parent)]
188+
Insert = 6, // ------- [Insert, target(parent), reference, ...nodes]
188189
}
189190

190191
export type VNodeJournal = Array<
@@ -968,6 +969,15 @@ export const vnode_applyJournal = (journal: VNodeJournal) => {
968969
idx++;
969970
}
970971
break;
972+
case VNodeJournalOpCode.RemoveAll:
973+
const removeAllParent = journal[idx++] as Element;
974+
if (removeAllParent.replaceChildren) {
975+
removeAllParent.replaceChildren();
976+
} else {
977+
// fallback if replaceChildren is not supported
978+
removeAllParent.textContent = '';
979+
}
980+
break;
971981
case VNodeJournalOpCode.Insert:
972982
const insertParent = journal[idx++] as Element;
973983
const insertBefore = journal[idx++] as Element | Text | null;
@@ -1220,8 +1230,7 @@ export const vnode_truncate = (
12201230
) => {
12211231
assertDefined(vDelete, 'Missing vDelete.');
12221232
const parent = vnode_getDomParent(vParent);
1223-
const children = vnode_getDOMChildNodes(journal, vDelete);
1224-
parent && children.length && journal.push(VNodeJournalOpCode.Remove, parent, ...children);
1233+
parent && journal.push(VNodeJournalOpCode.RemoveAll, parent);
12251234
const vPrevious = vDelete.previousSibling;
12261235
if (vPrevious) {
12271236
vPrevious.nextSibling = null;

packages/qwik/src/core/tests/component.spec.tsx

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2410,6 +2410,88 @@ describe.each([
24102410
await expect(document.querySelector('div')).toMatchDOM(<div />);
24112411
});
24122412

2413+
it('should correctly remove all children for empty array', async () => {
2414+
const Cmp = component$(() => {
2415+
const list = useSignal([1, 2, 3]);
2416+
return (
2417+
<main>
2418+
<button onClick$={() => (list.value = [])}>Remove</button>
2419+
{list.value.map((item) => (
2420+
<div>{item}</div>
2421+
))}
2422+
</main>
2423+
);
2424+
});
2425+
const { vNode, document } = await render(<Cmp />, { debug });
2426+
expect(vNode).toMatchVDOM(
2427+
<Component>
2428+
<main>
2429+
<button>Remove</button>
2430+
<div>1</div>
2431+
<div>2</div>
2432+
<div>3</div>
2433+
</main>
2434+
</Component>
2435+
);
2436+
await trigger(document.body, 'button', 'click');
2437+
expect(vNode).toMatchVDOM(
2438+
<Component>
2439+
<main>
2440+
<button>Remove</button>
2441+
</main>
2442+
</Component>
2443+
);
2444+
expect(document.querySelector('main')).toMatchDOM(
2445+
<main>
2446+
<button>Remove</button>
2447+
</main>
2448+
);
2449+
});
2450+
2451+
it('should correctly remove all children for empty array - case 2', async () => {
2452+
const Cmp = component$(() => {
2453+
const list = useSignal([1, 2, 3]);
2454+
return (
2455+
<main>
2456+
<button onClick$={() => (list.value = [])}>Remove</button>
2457+
<div>
2458+
{list.value.map((item) => (
2459+
<div>{item}</div>
2460+
))}
2461+
</div>
2462+
</main>
2463+
);
2464+
});
2465+
const { vNode, document } = await render(<Cmp />, { debug });
2466+
expect(vNode).toMatchVDOM(
2467+
<Component>
2468+
<main>
2469+
<button>Remove</button>
2470+
<div>
2471+
<div>1</div>
2472+
<div>2</div>
2473+
<div>3</div>
2474+
</div>
2475+
</main>
2476+
</Component>
2477+
);
2478+
await trigger(document.body, 'button', 'click');
2479+
expect(vNode).toMatchVDOM(
2480+
<Component>
2481+
<main>
2482+
<button>Remove</button>
2483+
<div></div>
2484+
</main>
2485+
</Component>
2486+
);
2487+
expect(document.querySelector('main')).toMatchDOM(
2488+
<main>
2489+
<button>Remove</button>
2490+
<div></div>
2491+
</main>
2492+
);
2493+
});
2494+
24132495
describe('regression', () => {
24142496
it('#3643', async () => {
24152497
const Issue3643 = component$(() => {

0 commit comments

Comments
 (0)