From b43c4c4dfd6c2ec28961842b4bc77bd01eb6cc4d Mon Sep 17 00:00:00 2001 From: HcySunYang Date: Sun, 25 Apr 2021 11:54:20 +0800 Subject: [PATCH 1/4] fix(runtime-core): the component VNode should be cloned when reusing it --- .../__tests__/rendererComponent.spec.ts | 35 +++++++++++++++++++ packages/runtime-core/src/vnode.ts | 12 +++++-- 2 files changed, 44 insertions(+), 3 deletions(-) diff --git a/packages/runtime-core/__tests__/rendererComponent.spec.ts b/packages/runtime-core/__tests__/rendererComponent.spec.ts index 02070d384e9..fc004045fd9 100644 --- a/packages/runtime-core/__tests__/rendererComponent.spec.ts +++ b/packages/runtime-core/__tests__/rendererComponent.spec.ts @@ -296,4 +296,39 @@ describe('renderer: component', () => { expect(serializeInner(root)).toBe(`

1

`) }) }) + + test('the component VNode should be cloned when reusing it', () => { + const Child = { + setup(props: any, { slots }: SetupContext) { + return () => { + const c = slots.default!() + return [c, c, c] + } + } + } + + const ids: number[] = [] + const Comp = { + render: () => h('h1'), + beforeUnmount() { + ids.push((this as any).$.uid) + } + } + + const App = { + setup() { + return () => { + return h(Child, () => [h(Comp)]) + } + } + } + + const root = nodeOps.createElement('div') + render(h(App), root) + expect(serializeInner(root)).toBe(`

`) + + render(null, root) + expect(serializeInner(root)).toBe(``) + expect(ids).toEqual([ids[0], ids[0] + 1, ids[0] + 2]) + }) }) diff --git a/packages/runtime-core/src/vnode.ts b/packages/runtime-core/src/vnode.ts index e46c9842619..6ea2896ef1b 100644 --- a/packages/runtime-core/src/vnode.ts +++ b/packages/runtime-core/src/vnode.ts @@ -584,11 +584,15 @@ export function normalizeVNode(child: VNodeChild): VNode { return createVNode(Comment) } else if (isArray(child)) { // fragment - return createVNode(Fragment, null, child) + return createVNode( + Fragment, + null, + child.map(c => (isVNode(c) ? cloneIfMounted(c) : c)) + ) } else if (typeof child === 'object') { // already vnode, this should be the most common since compiled templates // always produce all-vnode children arrays - return child.el === null ? child : cloneVNode(child) + return cloneIfMounted(child) } else { // strings and numbers return createVNode(Text, null, String(child)) @@ -597,7 +601,9 @@ export function normalizeVNode(child: VNodeChild): VNode { // optimized normalization for template-compiled render fns export function cloneIfMounted(child: VNode): VNode { - return child.el === null ? child : cloneVNode(child) + return child.el === null && child.component === null + ? child + : cloneVNode(child) } export function normalizeChildren(vnode: VNode, children: unknown) { From 7ec5f98067994a16139c57bfe5f32fe1dc8338e0 Mon Sep 17 00:00:00 2001 From: HcySunYang Date: Sun, 25 Apr 2021 12:33:24 +0800 Subject: [PATCH 2/4] chore: remove unnecessary check --- packages/runtime-core/src/vnode.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/runtime-core/src/vnode.ts b/packages/runtime-core/src/vnode.ts index 6ea2896ef1b..d0515e02b0a 100644 --- a/packages/runtime-core/src/vnode.ts +++ b/packages/runtime-core/src/vnode.ts @@ -601,9 +601,7 @@ export function normalizeVNode(child: VNodeChild): VNode { // optimized normalization for template-compiled render fns export function cloneIfMounted(child: VNode): VNode { - return child.el === null && child.component === null - ? child - : cloneVNode(child) + return child.el === null ? child : cloneVNode(child) } export function normalizeChildren(vnode: VNode, children: unknown) { From c89fdb33f7ddd41fa2286d534a3800b67d07f25f Mon Sep 17 00:00:00 2001 From: HcySunYang Date: Sun, 25 Apr 2021 13:48:39 +0800 Subject: [PATCH 3/4] refactor: simple way --- packages/runtime-core/src/vnode.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/runtime-core/src/vnode.ts b/packages/runtime-core/src/vnode.ts index d0515e02b0a..d76173c58ac 100644 --- a/packages/runtime-core/src/vnode.ts +++ b/packages/runtime-core/src/vnode.ts @@ -587,7 +587,8 @@ export function normalizeVNode(child: VNodeChild): VNode { return createVNode( Fragment, null, - child.map(c => (isVNode(c) ? cloneIfMounted(c) : c)) + // #3666, avoid reference pollution when reusing vnode + child.map(c => c) ) } else if (typeof child === 'object') { // already vnode, this should be the most common since compiled templates From a857a8c591f2ad629a70e9a892980613d34c7882 Mon Sep 17 00:00:00 2001 From: HcySunYang Date: Wed, 28 Apr 2021 19:16:41 +0800 Subject: [PATCH 4/4] perf: use slice instead of map --- packages/runtime-core/src/vnode.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/runtime-core/src/vnode.ts b/packages/runtime-core/src/vnode.ts index d76173c58ac..a8d1c08c28e 100644 --- a/packages/runtime-core/src/vnode.ts +++ b/packages/runtime-core/src/vnode.ts @@ -588,7 +588,7 @@ export function normalizeVNode(child: VNodeChild): VNode { Fragment, null, // #3666, avoid reference pollution when reusing vnode - child.map(c => c) + child.slice() ) } else if (typeof child === 'object') { // already vnode, this should be the most common since compiled templates