fix: ensure functionalContext is cloned during slot clones

fix #7106
This commit is contained in:
Evan You 2017-11-22 16:37:24 -05:00
parent 3932a451a1
commit 604e081d04
6 changed files with 63 additions and 15 deletions

View File

@ -20,7 +20,7 @@ export function resolveSlots (
}
// named slots should only be respected if the vnode was rendered in the
// same context.
if ((child.context === context || child.functionalContext === context) &&
if ((child.context === context || child.fnContext === context) &&
data && data.slot != null
) {
const name = child.data.slot

View File

@ -49,8 +49,8 @@ function FunctionalRenderContext (
this._c = (a, b, c, d) => {
const vnode: ?VNode = createElement(contextVm, a, b, c, d, needNormalization)
if (vnode) {
vnode.functionalScopeId = options._scopeId
vnode.functionalContext = parent
vnode.fnScopeId = options._scopeId
vnode.fnContext = parent
}
return vnode
}
@ -91,8 +91,8 @@ export function createFunctionalComponent (
const vnode = options.render.call(null, renderContext._c, renderContext)
if (vnode instanceof VNode) {
vnode.functionalContext = contextVm
vnode.functionalOptions = options
vnode.fnContext = contextVm
vnode.fnOptions = options
if (data.slot) {
(vnode.data || (vnode.data = {})).slot = data.slot
}

View File

@ -294,7 +294,7 @@ export function createPatchFunction (backend) {
// of going through the normal attribute patching process.
function setScope (vnode) {
let i
if (isDef(i = vnode.functionalScopeId)) {
if (isDef(i = vnode.fnScopeId)) {
nodeOps.setAttribute(vnode.elm, i, '')
} else {
let ancestor = vnode
@ -308,7 +308,7 @@ export function createPatchFunction (backend) {
// for slot content they should also get the scopeId from the host instance.
if (isDef(i = activeInstance) &&
i !== vnode.context &&
i !== vnode.functionalContext &&
i !== vnode.fnContext &&
isDef(i = i.$options._scopeId)
) {
nodeOps.setAttribute(vnode.elm, i, '')

View File

@ -24,9 +24,9 @@ export default class VNode {
asyncMeta: Object | void;
isAsyncPlaceholder: boolean;
ssrContext: Object | void;
functionalContext: Component | void; // real context vm for functional nodes
functionalOptions: ?ComponentOptions; // for SSR caching
functionalScopeId: ?string; // functioanl scope id support
fnContext: Component | void; // real context vm for functional nodes
fnOptions: ?ComponentOptions; // for SSR caching
fnScopeId: ?string; // functioanl scope id support
constructor (
tag?: string,
@ -45,9 +45,9 @@ export default class VNode {
this.elm = elm
this.ns = undefined
this.context = context
this.functionalContext = undefined
this.functionalOptions = undefined
this.functionalScopeId = undefined
this.fnContext = undefined
this.fnOptions = undefined
this.fnScopeId = undefined
this.key = data && data.key
this.componentOptions = componentOptions
this.componentInstance = undefined
@ -101,6 +101,9 @@ export function cloneVNode (vnode: VNode, deep?: boolean): VNode {
cloned.isStatic = vnode.isStatic
cloned.key = vnode.key
cloned.isComment = vnode.isComment
cloned.fnContext = vnode.fnContext
cloned.fnOptions = vnode.fnOptions
cloned.fnScopeId = vnode.fnScopeId
cloned.isCloned = true
if (deep) {
if (vnode.children) {

View File

@ -252,8 +252,8 @@ function renderElement (el, isRoot, context) {
el.data.attrs[SSR_ATTR] = 'true'
}
if (el.functionalOptions) {
registerComponentForCache(el.functionalOptions, write)
if (el.fnOptions) {
registerComponentForCache(el.fnOptions, write)
}
const startTag = renderStartingTag(el, context)

View File

@ -779,4 +779,49 @@ describe('Component slot', () => {
expect(vm.$el.innerHTML).toBe('<div class="foo"><div class="bar">fallback</div></div>')
})
// #7106
it('should not lose functional slot across renders', done => {
const One = {
data: () => ({
foo: true
}),
render (h) {
this.foo
return h('div', this.$slots.slot)
}
}
const Two = {
render (h) {
return h('span', this.$slots.slot)
}
}
const Three = {
functional: true,
render: (h, { children }) => h('span', children)
}
const vm = new Vue({
template: `
<div>
<one ref="one">
<two slot="slot">
<three slot="slot">hello</three>
</two>
</one>
</div>
`,
components: { One, Two, Three }
}).$mount()
expect(vm.$el.textContent).toBe('hello')
// trigger re-render of <one>
vm.$refs.one.foo = false
waitForUpdate(() => {
// should still be there
expect(vm.$el.textContent).toBe('hello')
}).then(done)
})
})