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

fix: deep clone slot vnodes on re-render

fix #6372
Evan You 8 лет назад
Родитель
Сommit
0529040c17

+ 6 - 2
src/core/instance/render.js

@@ -78,9 +78,13 @@ export function renderMixin (Vue: Class<Component>) {
     } = vm.$options
 
     if (vm._isMounted) {
-      // clone slot nodes on re-renders
+      // if the parent didn't update, the slot nodes will be the ones from
+      // last render. They need to be cloned to ensure "freshness" for this render.
       for (const key in vm.$slots) {
-        vm.$slots[key] = cloneVNodes(vm.$slots[key])
+        const slot = vm.$slots[key]
+        if (slot._rendered) {
+          vm.$slots[key] = cloneVNodes(slot, true /* deep */)
+        }
       }
     }
 

+ 6 - 3
src/core/vdom/vnode.js

@@ -79,7 +79,7 @@ export function createTextVNode (val: string | number) {
 // used for static nodes and slot nodes because they may be reused across
 // multiple renders, cloning them avoids errors when DOM manipulations rely
 // on their elm reference.
-export function cloneVNode (vnode: VNode): VNode {
+export function cloneVNode (vnode: VNode, deep?: boolean): VNode {
   const cloned = new VNode(
     vnode.tag,
     vnode.data,
@@ -95,14 +95,17 @@ export function cloneVNode (vnode: VNode): VNode {
   cloned.key = vnode.key
   cloned.isComment = vnode.isComment
   cloned.isCloned = true
+  if (deep) {
+    cloned.children = vnode.children && cloneVNodes(vnode.children)
+  }
   return cloned
 }
 
-export function cloneVNodes (vnodes: Array<VNode>): Array<VNode> {
+export function cloneVNodes (vnodes: Array<VNode>, deep?: boolean): Array<VNode> {
   const len = vnodes.length
   const res = new Array(len)
   for (let i = 0; i < len; i++) {
-    res[i] = cloneVNode(vnodes[i])
+    res[i] = cloneVNode(vnodes[i], deep)
   }
   return res
 }

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

@@ -685,4 +685,47 @@ describe('Component slot', () => {
     }).$mount()
     expect(vm.$el.innerHTML).toBe('<div>default<span>foo</span></div>')
   })
+
+  it('should handle nested components in slots properly', done => {
+    const TestComponent = {
+      template: `
+        <component :is="toggleEl ? 'b' : 'i'">
+          <slot />
+        </component>
+      `,
+      data () {
+        return {
+          toggleEl: true
+        }
+      }
+    }
+
+    const vm = new Vue({
+      template: `
+        <div>
+          <test-component ref="test">
+            <div>
+              <foo/>
+            </div><bar/>
+          </test-component>
+        </div>
+      `,    
+      components: {
+        TestComponent,
+        foo: {
+          template: `<div>foo</div>`
+        },
+        bar: {
+          template: `<div>bar</div>`
+        }
+      }
+    }).$mount()
+
+    expect(vm.$el.innerHTML).toBe(`<b><div><div>foo</div></div><div>bar</div></b>`)
+
+    vm.$refs.test.toggleEl = false
+    waitForUpdate(() => {
+      expect(vm.$el.innerHTML).toBe(`<i><div><div>foo</div></div><div>bar</div></i>`)
+    }).then(done)
+  })
 })