diff --git a/src/core/vdom/create-functional-component.js b/src/core/vdom/create-functional-component.js index fb21cdab218..efed801db7d 100644 --- a/src/core/vdom/create-functional-component.js +++ b/src/core/vdom/create-functional-component.js @@ -1,6 +1,6 @@ /* @flow */ -import VNode from './vnode' +import VNode, { cloneVNode } from './vnode' import { createElement } from './create-element' import { resolveInject } from '../instance/inject' import { normalizeChildren } from '../vdom/helpers/normalize-children' @@ -32,6 +32,9 @@ export function FunctionalRenderContext ( // $flow-disable-line contextVm._original = parent } else { + // the context vm passed in is a functional context as well. + // in this case we want to make sure we are able to get a hold to the + // real context instance. contextVm = parent // $flow-disable-line parent = parent._original @@ -102,23 +105,28 @@ export function createFunctionalComponent ( const vnode = options.render.call(null, renderContext._c, renderContext) if (vnode instanceof VNode) { - setFunctionalContextForVNode(vnode, data, contextVm, options) - return vnode + return cloneAndMarkFunctionalResult(vnode, data, renderContext.parent, options) } else if (Array.isArray(vnode)) { const vnodes = normalizeChildren(vnode) || [] + const res = new Array(vnodes.length) for (let i = 0; i < vnodes.length; i++) { - setFunctionalContextForVNode(vnodes[i], data, contextVm, options) + res[i] = cloneAndMarkFunctionalResult(vnodes[i], data, renderContext.parent, options) } - return vnodes + return res } } -function setFunctionalContextForVNode (vnode, data, vm, options) { - vnode.fnContext = vm - vnode.fnOptions = options +function cloneAndMarkFunctionalResult (vnode, data, contextVm, options) { + // #7817 clone node before setting fnContext, otherwise if the node is reused + // (e.g. it was from a cached normal slot) the fnContext causes named slots + // that should not be matched to match. + const clone = cloneVNode(vnode) + clone.fnContext = contextVm + clone.fnOptions = options if (data.slot) { - (vnode.data || (vnode.data = {})).slot = data.slot + (clone.data || (clone.data = {})).slot = data.slot } + return clone } function mergeProps (to, from) { diff --git a/test/unit/features/component/component-slot.spec.js b/test/unit/features/component/component-slot.spec.js index 286dcb9cce9..e667a46ef89 100644 --- a/test/unit/features/component/component-slot.spec.js +++ b/test/unit/features/component/component-slot.spec.js @@ -855,4 +855,35 @@ describe('Component slot', () => { expect(vm.$el.textContent).toBe('foo') }) + + // #7817 + it('should not match wrong named slot in functional component on re-render', done => { + const Functional = { + functional: true, + render: (h, ctx) => ctx.slots().default + } + + const Stateful = { + data () { + return { ok: true } + }, + render (h) { + this.ok // register dep + return h('div', [ + h(Functional, this.$slots.named) + ]) + } + } + + const vm = new Vue({ + template: `
foo
`, + components: { Stateful } + }).$mount() + + expect(vm.$el.textContent).toBe('foo') + vm.$refs.stateful.ok = false + waitForUpdate(() => { + expect(vm.$el.textContent).toBe('foo') + }).then(done) + }) })