Skip to content

Commit 21908b7

Browse files
committed
ensure children is only re-resolved on parent update (fix #3400)
1 parent 1c0effa commit 21908b7

File tree

3 files changed

+90
-8
lines changed

3 files changed

+90
-8
lines changed

src/core/instance/lifecycle.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import Watcher from '../observer/watcher'
44
import { emptyVNode } from '../vdom/vnode'
55
import { observerState } from '../observer/index'
66
import { warn, validateProp, remove, noop } from '../util/index'
7+
import { resolveSlots } from './render'
78

89
export let activeInstance: any = null
910

@@ -137,8 +138,9 @@ export function lifecycleMixin (Vue: Class<Component>) {
137138
vm.$options._parentListeners = listeners
138139
vm._updateListeners(listeners, oldListeners)
139140
}
140-
// force udpate if has children
141+
// resolve slots + force update if has children
141142
if (hasChildren) {
143+
vm.$slots = resolveSlots(renderChildren)
142144
vm.$forceUpdate()
143145
}
144146
}

src/core/instance/render.js

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ export function initRender (vm: Component) {
1414
vm.$vnode = null // the placeholder node in parent tree
1515
vm._vnode = null // the root of the child tree
1616
vm._staticTrees = null
17-
vm.$slots = {}
17+
vm.$slots = resolveSlots(vm.$options._renderChildren)
1818
// bind the public createElement fn to this instance
1919
// so that we get proper render context inside it.
2020
vm.$createElement = bind(createElement, vm)
@@ -33,7 +33,6 @@ export function renderMixin (Vue: Class<Component>) {
3333
const {
3434
render,
3535
staticRenderFns,
36-
_renderChildren,
3736
_parentVnode
3837
} = vm.$options
3938

@@ -43,9 +42,6 @@ export function renderMixin (Vue: Class<Component>) {
4342
// set parent vnode. this allows render functions to have access
4443
// to the data on the placeholder node.
4544
vm.$vnode = _parentVnode
46-
// resolve slots. becaues slots are rendered in parent scope,
47-
// we set the activeInstance to parent.
48-
vm.$slots = resolveSlots(_renderChildren)
4945
// render self
5046
let vnode
5147
try {
@@ -171,7 +167,9 @@ export function renderMixin (Vue: Class<Component>) {
171167
}
172168
}
173169

174-
export function resolveSlots (renderChildren: ?VNodeChildren): Object {
170+
export function resolveSlots (
171+
renderChildren: ?VNodeChildren
172+
): { [key: string]: Array<VNode> } {
175173
const slots = {}
176174
if (!renderChildren) {
177175
return slots

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

Lines changed: 83 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,7 @@ describe('Component slot', () => {
313313
expect('Render function should return a single root node').toHaveBeenWarned()
314314
})
315315

316+
// #3254
316317
it('should not keep slot name when passed further down', () => {
317318
const vm = new Vue({
318319
template: '<test><span slot="foo">foo<span></test>',
@@ -321,7 +322,12 @@ describe('Component slot', () => {
321322
template: '<child><slot name="foo"></slot></child>',
322323
components: {
323324
child: {
324-
template: '<div><div class="default"><slot></slot></div><div class="named"><slot name="foo"></slot></div></div>'
325+
template: `
326+
<div>
327+
<div class="default"><slot></slot></div>
328+
<div class="named"><slot name="foo"></slot></div>
329+
</div>
330+
`
325331
}
326332
}
327333
}
@@ -330,4 +336,80 @@ describe('Component slot', () => {
330336
expect(vm.$el.querySelector('.default').textContent).toBe('foo')
331337
expect(vm.$el.querySelector('.named').textContent).toBe('')
332338
})
339+
340+
it('should not keep slot name when passed further down (nested)', () => {
341+
const vm = new Vue({
342+
template: '<wrap><test><span slot="foo">foo<span></test></wrap>',
343+
components: {
344+
wrap: {
345+
template: '<div><slot></slot></div>'
346+
},
347+
test: {
348+
template: '<child><slot name="foo"></slot></child>',
349+
components: {
350+
child: {
351+
template: `
352+
<div>
353+
<div class="default"><slot></slot></div>
354+
<div class="named"><slot name="foo"></slot></div>
355+
</div>
356+
`
357+
}
358+
}
359+
}
360+
}
361+
}).$mount()
362+
expect(vm.$el.querySelector('.default').textContent).toBe('foo')
363+
expect(vm.$el.querySelector('.named').textContent).toBe('')
364+
})
365+
366+
it('should not keep slot name when passed further down (functional)', () => {
367+
const child = {
368+
template: `
369+
<div>
370+
<div class="default"><slot></slot></div>
371+
<div class="named"><slot name="foo"></slot></div>
372+
</div>
373+
`
374+
}
375+
const vm = new Vue({
376+
template: '<test><span slot="foo">foo<span></test>',
377+
components: {
378+
test: {
379+
functional: true,
380+
render (h, ctx) {
381+
const slots = ctx.slots()
382+
return h(child, slots.foo)
383+
}
384+
}
385+
}
386+
}).$mount()
387+
console.log(vm.$el.innerHTML)
388+
expect(vm.$el.querySelector('.default').textContent).toBe('foo')
389+
expect(vm.$el.querySelector('.named').textContent).toBe('')
390+
})
391+
392+
// #3400
393+
it('named slots should be consistent across re-renders', done => {
394+
const vm = new Vue({
395+
template: `
396+
<comp>
397+
<div slot="foo">foo</div>
398+
</comp>
399+
`,
400+
components: {
401+
comp: {
402+
data () {
403+
return { a: 1 }
404+
},
405+
template: `<div><slot name="foo"></slot>{{ a }}</div>`
406+
}
407+
}
408+
}).$mount()
409+
expect(vm.$el.textContent).toBe('foo1')
410+
vm.$children[0].a = 2
411+
waitForUpdate(() => {
412+
expect(vm.$el.textContent).toBe('foo2')
413+
}).then(done)
414+
})
333415
})

0 commit comments

Comments
 (0)