Skip to content

Commit 8c54457

Browse files
committed
fix(slot): slots should be deep cloned, fix vuejs#7975
1 parent 52719cc commit 8c54457

File tree

3 files changed

+88
-7
lines changed

3 files changed

+88
-7
lines changed

src/core/instance/render.js

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import {
1111
import { createElement } from '../vdom/create-element'
1212
import { installRenderHelpers } from './render-helpers/index'
1313
import { resolveSlots } from './render-helpers/resolve-slots'
14-
import VNode, { createEmptyVNode } from '../vdom/vnode'
14+
import VNode, { cloneVNodes, createEmptyVNode } from '../vdom/vnode'
1515

1616
import { isUpdatingChildComponent } from './lifecycle'
1717

@@ -62,11 +62,16 @@ export function renderMixin (Vue: Class<Component>) {
6262
const vm: Component = this
6363
const { render, _parentVnode } = vm.$options
6464

65-
// reset _rendered flag on slots for duplicate slot check
66-
if (process.env.NODE_ENV !== 'production') {
65+
if (vm._isMounted) {
66+
// if the parent didn't update, the slot nodes will be the ones from
67+
// last render. They need to be cloned to ensure "freshness" for this render.
6768
for (const key in vm.$slots) {
68-
// $flow-disable-line
69-
vm.$slots[key]._rendered = false
69+
const slot = vm.$slots[key]
70+
// _rendered is a flag added by renderSlot, but may not be present
71+
// if the slot is passed from manually written render functions
72+
if (slot._rendered || (slot[0] && slot[0].elm)) {
73+
vm.$slots[key] = cloneVNodes(slot, true /* deep */)
74+
}
7075
}
7176
}
7277

src/core/vdom/vnode.js

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,15 +85,16 @@ export function createTextVNode (val: string | number) {
8585
// used for static nodes and slot nodes because they may be reused across
8686
// multiple renders, cloning them avoids errors when DOM manipulations rely
8787
// on their elm reference.
88-
export function cloneVNode (vnode: VNode): VNode {
88+
export function cloneVNode (vnode: VNode, deep?: boolean): VNode {
89+
const componentOptions = vnode.componentOptions
8990
const cloned = new VNode(
9091
vnode.tag,
9192
vnode.data,
9293
vnode.children,
9394
vnode.text,
9495
vnode.elm,
9596
vnode.context,
96-
vnode.componentOptions,
97+
componentOptions,
9798
vnode.asyncFactory
9899
)
99100
cloned.ns = vnode.ns
@@ -105,5 +106,23 @@ export function cloneVNode (vnode: VNode): VNode {
105106
cloned.fnScopeId = vnode.fnScopeId
106107
cloned.asyncMeta = vnode.asyncMeta
107108
cloned.isCloned = true
109+
110+
if (deep) {
111+
if (vnode.children) {
112+
cloned.children = cloneVNodes(vnode.children, true)
113+
}
114+
if (componentOptions && componentOptions.children) {
115+
componentOptions.children = cloneVNodes(componentOptions.children, true)
116+
}
117+
}
108118
return cloned
109119
}
120+
121+
export function cloneVNodes (vnodes: Array<VNode>, deep?: boolean): Array<VNode> {
122+
const len = vnodes.length
123+
const res = new Array(len)
124+
for (let i = 0; i < len; i++) {
125+
res[i] = cloneVNode(vnodes[i], deep)
126+
}
127+
return res
128+
}

test/unit/features/component/component-slot.spec.js

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -886,4 +886,61 @@ describe('Component slot', () => {
886886
expect(vm.$el.textContent).toBe('foo')
887887
}).then(done)
888888
})
889+
890+
// #7975
891+
it('should update named slot correctly when its position in the tree changed', done => {
892+
const ChildComponent = {
893+
template: '<b>{{ message }}</b>',
894+
props: ['message']
895+
}
896+
897+
let parentVm
898+
const ParentComponent = {
899+
template: `
900+
<div>
901+
<span v-if="alter">
902+
<span><slot name="foo" /></span>
903+
</span>
904+
<span v-else>
905+
<slot name="foo" />
906+
</span>
907+
</div>
908+
`,
909+
data () {
910+
return {
911+
alter: true
912+
}
913+
},
914+
mounted () {
915+
parentVm = this
916+
}
917+
}
918+
919+
const vm = new Vue({
920+
template: `
921+
<parent-component>
922+
<span slot="foo">
923+
<child-component :message="message" />
924+
</span>
925+
</parent-component>
926+
`,
927+
components: {
928+
ChildComponent,
929+
ParentComponent
930+
},
931+
data () {
932+
return {
933+
message: 1
934+
}
935+
}
936+
}).$mount()
937+
938+
expect(vm.$el.firstChild.innerHTML).toBe('<span><span><b>1</b></span></span>')
939+
parentVm.alter = false
940+
waitForUpdate(() => {
941+
vm.message = 2
942+
}).then(() => {
943+
expect(vm.$el.firstChild.innerHTML).toBe('<span><b>2</b></span>')
944+
}).then(done)
945+
})
889946
})

0 commit comments

Comments
 (0)