Просмотр исходного кода

fix(runtime-core): fix cases of reused children arrays in render functions (#3670)

fix #3666
HcySunYang 4 лет назад
Родитель
Сommit
a641eb201f

+ 35 - 0
packages/runtime-core/__tests__/rendererComponent.spec.ts

@@ -296,4 +296,39 @@ describe('renderer: component', () => {
       expect(serializeInner(root)).toBe(`<h1>1</h1>`)
     })
   })
+
+  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(`<h1></h1><h1></h1><h1></h1>`)
+
+    render(null, root)
+    expect(serializeInner(root)).toBe(``)
+    expect(ids).toEqual([ids[0], ids[0] + 1, ids[0] + 2])
+  })
 })

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