Browse Source

fix(core): avoid mutating original children when cloning vnode

The on-demand clone strategy introduced in 956756b mutates the owner
array of the cloned vnode. This causes the newly cloned vnode to be
destroyed when the parent node is destroyed. This is fixed by cloning
the children array when cloning a vnode.

fix #7975
Evan You 7 years ago
parent
commit
097f6229df
2 changed files with 58 additions and 1 deletions
  1. 4 1
      src/core/vdom/vnode.js
  2. 54 0
      test/unit/features/component/component-slot.spec.js

+ 4 - 1
src/core/vdom/vnode.js

@@ -90,7 +90,10 @@ export function cloneVNode (vnode: VNode): VNode {
   const cloned = new VNode(
     vnode.tag,
     vnode.data,
-    vnode.children,
+    // #7975
+    // clone children array to avoid mutating original in case of cloning
+    // a child.
+    vnode.children && vnode.children.slice(),
     vnode.text,
     vnode.elm,
     vnode.context,

+ 54 - 0
test/unit/features/component/component-slot.spec.js

@@ -886,4 +886,58 @@ describe('Component slot', () => {
       expect(vm.$el.textContent).toBe('foo')
     }).then(done)
   })
+
+  // #7975
+  it('should update named slot correctly when its position in the tree changed', done => {
+    const ChildComponent = {
+      template: '<b>{{ message }}</b>',
+      props: ['message']
+    }
+     let parentVm
+    const ParentComponent = {
+      template: `
+        <div>
+          <span v-if="alter">
+            <span><slot name="foo" /></span>
+          </span>
+          <span v-else>
+            <slot name="foo" />
+          </span>
+        </div>
+      `,
+      data () {
+        return {
+          alter: true
+        }
+      },
+      mounted () {
+        parentVm = this
+      }
+    }
+     const vm = new Vue({
+      template: `
+        <parent-component>
+          <span slot="foo">
+            <child-component :message="message" />
+          </span>
+        </parent-component>
+      `,
+      components: {
+        ChildComponent,
+        ParentComponent
+      },
+      data () {
+        return {
+          message: 1
+        }
+      }
+    }).$mount()
+     expect(vm.$el.firstChild.innerHTML).toBe('<span><span><b>1</b></span></span>')
+    parentVm.alter = false
+    waitForUpdate(() => {
+      vm.message = 2
+    }).then(() => {
+      expect(vm.$el.firstChild.innerHTML).toBe('<span><b>2</b></span>')
+    }).then(done)
+  })
 })