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))