From a641eb201fe51620d50884b988f6fefc3e21a20b Mon Sep 17 00:00:00 2001 From: HcySunYang Date: Fri, 28 May 2021 05:06:55 +0800 Subject: [PATCH] fix(runtime-core): fix cases of reused children arrays in render functions (#3670) fix #3666 --- .../__tests__/rendererComponent.spec.ts | 35 +++++++++++++++++++ packages/runtime-core/src/vnode.ts | 9 +++-- 2 files changed, 42 insertions(+), 2 deletions(-) diff --git a/packages/runtime-core/__tests__/rendererComponent.spec.ts b/packages/runtime-core/__tests__/rendererComponent.spec.ts index 02070d38..fc004045 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 7109287d..da99f2ec 100644 --- a/packages/runtime-core/src/vnode.ts +++ b/packages/runtime-core/src/vnode.ts @@ -603,11 +603,16 @@ export function normalizeVNode(child: VNodeChild): VNode { return createVNode(Comment) } else if (isArray(child)) { // fragment - return createVNode(Fragment, null, child) + return createVNode( + Fragment, + null, + // #3666, avoid reference pollution when reusing vnode + child.slice() + ) } 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))