Skip to content

Commit

Permalink
fix(slot): slots should be deep cloned, fix vuejs#7975
Browse files Browse the repository at this point in the history
  • Loading branch information
haoqunjiang committed Jun 28, 2018
1 parent 52719cc commit 8c54457
Show file tree
Hide file tree
Showing 3 changed files with 88 additions and 7 deletions.
15 changes: 10 additions & 5 deletions src/core/instance/render.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
import { createElement } from '../vdom/create-element'
import { installRenderHelpers } from './render-helpers/index'
import { resolveSlots } from './render-helpers/resolve-slots'
import VNode, { createEmptyVNode } from '../vdom/vnode'
import VNode, { cloneVNodes, createEmptyVNode } from '../vdom/vnode'

import { isUpdatingChildComponent } from './lifecycle'

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

// reset _rendered flag on slots for duplicate slot check
if (process.env.NODE_ENV !== 'production') {
if (vm._isMounted) {
// 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) {
// $flow-disable-line
vm.$slots[key]._rendered = false
const slot = vm.$slots[key]
// _rendered is a flag added by renderSlot, but may not be present
// if the slot is passed from manually written render functions
if (slot._rendered || (slot[0] && slot[0].elm)) {
vm.$slots[key] = cloneVNodes(slot, true /* deep */)
}
}
}

Expand Down
23 changes: 21 additions & 2 deletions src/core/vdom/vnode.js
Original file line number Diff line number Diff line change
Expand Up @@ -85,15 +85,16 @@ 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 componentOptions = vnode.componentOptions
const cloned = new VNode(
vnode.tag,
vnode.data,
vnode.children,
vnode.text,
vnode.elm,
vnode.context,
vnode.componentOptions,
componentOptions,
vnode.asyncFactory
)
cloned.ns = vnode.ns
Expand All @@ -105,5 +106,23 @@ export function cloneVNode (vnode: VNode): VNode {
cloned.fnScopeId = vnode.fnScopeId
cloned.asyncMeta = vnode.asyncMeta
cloned.isCloned = true

if (deep) {
if (vnode.children) {
cloned.children = cloneVNodes(vnode.children, true)
}
if (componentOptions && componentOptions.children) {
componentOptions.children = cloneVNodes(componentOptions.children, true)
}
}
return cloned
}

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], deep)
}
return res
}
57 changes: 57 additions & 0 deletions test/unit/features/component/component-slot.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -886,4 +886,61 @@ 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)
})
})

0 comments on commit 8c54457

Please sign in to comment.