From 8c5445737b6eb71947a80042e85da301f8c85632 Mon Sep 17 00:00:00 2001 From: Haoqun Jiang Date: Thu, 28 Jun 2018 18:04:06 +0800 Subject: [PATCH] fix(slot): slots should be deep cloned, fix #7975 --- src/core/instance/render.js | 15 +++-- src/core/vdom/vnode.js | 23 +++++++- .../features/component/component-slot.spec.js | 57 +++++++++++++++++++ 3 files changed, 88 insertions(+), 7 deletions(-) diff --git a/src/core/instance/render.js b/src/core/instance/render.js index 70fb2c0e57a..c4e0c731ad6 100644 --- a/src/core/instance/render.js +++ b/src/core/instance/render.js @@ -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' @@ -62,11 +62,16 @@ export function renderMixin (Vue: Class) { 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 */) + } } } diff --git a/src/core/vdom/vnode.js b/src/core/vdom/vnode.js index c3e00078fea..9978fedea8d 100644 --- a/src/core/vdom/vnode.js +++ b/src/core/vdom/vnode.js @@ -85,7 +85,8 @@ 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, @@ -93,7 +94,7 @@ export function cloneVNode (vnode: VNode): VNode { vnode.text, vnode.elm, vnode.context, - vnode.componentOptions, + componentOptions, vnode.asyncFactory ) cloned.ns = vnode.ns @@ -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, deep?: boolean): Array { + const len = vnodes.length + const res = new Array(len) + for (let i = 0; i < len; i++) { + res[i] = cloneVNode(vnodes[i], deep) + } + return res +} diff --git a/test/unit/features/component/component-slot.spec.js b/test/unit/features/component/component-slot.spec.js index e667a46ef89..13c77e569ad 100644 --- a/test/unit/features/component/component-slot.spec.js +++ b/test/unit/features/component/component-slot.spec.js @@ -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: '{{ message }}', + props: ['message'] + } + + let parentVm + const ParentComponent = { + template: ` +
+ + + + + + +
+ `, + data () { + return { + alter: true + } + }, + mounted () { + parentVm = this + } + } + + const vm = new Vue({ + template: ` + + + + + + `, + components: { + ChildComponent, + ParentComponent + }, + data () { + return { + message: 1 + } + } + }).$mount() + + expect(vm.$el.firstChild.innerHTML).toBe('1') + parentVm.alter = false + waitForUpdate(() => { + vm.message = 2 + }).then(() => { + expect(vm.$el.firstChild.innerHTML).toBe('2') + }).then(done) + }) })