Skip to content

Commit 4afccc8

Browse files
committed
proper slot duplication warning (fix vuejs#3595)
1 parent f25482d commit 4afccc8

File tree

6 files changed

+61
-49
lines changed

6 files changed

+61
-49
lines changed

flow/component.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,8 @@ declare interface Component {
9393
_f: (id: string) => Function;
9494
// renderList
9595
_l: (val: any, render: Function) => ?Array<VNode>;
96+
// renderSlot
97+
_t: (name: string, fallback: ?Array<VNode>) => ?Array<VNode>;
9698
// apply v-bind object
9799
_b: (vnode: VNodeWithData, value: any) => void;
98100
// retrive custom keyCode

src/compiler/codegen/index.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -221,11 +221,11 @@ function genText (text: ASTText | ASTExpression): string {
221221
}
222222

223223
function genSlot (el: ASTElement): string {
224-
const slot = `$slots[${el.slotName || '"default"'}]`
224+
const slotName = el.slotName || '"default"'
225225
const children = genChildren(el)
226226
return children
227-
? `(${slot}||${children})`
228-
: slot
227+
? `_t(${slotName},${children})`
228+
: `_t(${slotName})`
229229
}
230230

231231
function genComponent (el: any): string {

src/compiler/parser/index.js

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@ let preTransforms
3434
let transforms
3535
let postTransforms
3636
let delimiters
37-
let seenSlots: any
3837

3938
/**
4039
* Convert HTML string to AST.
@@ -51,7 +50,6 @@ export function parse (
5150
transforms = pluckModuleFunction(options.modules, 'transformNode')
5251
postTransforms = pluckModuleFunction(options.modules, 'postTransformNode')
5352
delimiters = options.delimiters
54-
seenSlots = Object.create(null)
5553
const stack = []
5654
const preserveWhitespace = options.preserveWhitespace !== false
5755
let root
@@ -326,25 +324,7 @@ function processOnce (el) {
326324

327325
function processSlot (el) {
328326
if (el.tag === 'slot') {
329-
if (process.env.NODE_ENV !== 'production') {
330-
if (!el.attrsMap[':name'] && !el.attrsMap['v-bind:name'] && checkInFor(el)) {
331-
warn(
332-
'Static <slot> found inside v-for: they will not render correctly. ' +
333-
'Render the list in parent scope and use a single <slot> instead.'
334-
)
335-
}
336-
}
337327
el.slotName = getBindingAttr(el, 'name')
338-
if (process.env.NODE_ENV !== 'production') {
339-
const name = el.slotName
340-
if (seenSlots[name]) {
341-
warn(
342-
`Duplicate ${name ? `<slot> with name ${name}` : `default <slot>`} ` +
343-
`found in the same template.`
344-
)
345-
}
346-
seenSlots[name] = true
347-
}
348328
} else {
349329
const slotTarget = getBindingAttr(el, 'slot')
350330
if (slotTarget) {

src/core/instance/render.js

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -36,13 +36,6 @@ export function renderMixin (Vue: Class<Component>) {
3636
_parentVnode
3737
} = vm.$options
3838

39-
if (vm._isMounted) {
40-
// clone slot nodes on re-renders
41-
for (const key in vm.$slots) {
42-
vm.$slots[key] = cloneVNodes(vm.$slots[key])
43-
}
44-
}
45-
4639
if (staticRenderFns && !vm._staticTrees) {
4740
vm._staticTrees = []
4841
}
@@ -155,6 +148,30 @@ export function renderMixin (Vue: Class<Component>) {
155148
return ret
156149
}
157150

151+
// renderSlot
152+
Vue.prototype._t = function (
153+
name: string,
154+
fallback: ?Array<VNode>
155+
): ?Array<VNode> {
156+
let slotNodes = this.$slots[name]
157+
if (slotNodes) {
158+
// warn duplicate slot usage
159+
if (process.env.NODE_ENV !== 'production') {
160+
slotNodes._rendered && warn(
161+
`Duplicate presense of slot "${name}" found in the same render tree ` +
162+
`- this will likely cause render errors.`,
163+
this
164+
)
165+
slotNodes._rendered = true
166+
}
167+
// clone slot nodes on re-renders
168+
if (this._isMounted) {
169+
slotNodes = cloneVNodes(slotNodes)
170+
}
171+
}
172+
return slotNodes || fallback
173+
}
174+
158175
// apply v-bind object
159176
Vue.prototype._b = function bindProps (
160177
vnode: VNodeWithData,

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

Lines changed: 29 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -458,28 +458,41 @@ describe('Component slot', () => {
458458
it('warn duplicate slots', () => {
459459
new Vue({
460460
template: `<div>
461-
<slot></slot><slot></slot>
462-
<slot name="a"></slot><slot name="a"></slot>
463-
</div>`
461+
<test>
462+
<div>foo</div>
463+
<div slot="a">bar</div>
464+
</test>
465+
</div>`,
466+
components: {
467+
test: {
468+
template: `<div>
469+
<slot></slot><slot></slot>
470+
<div v-for="i in 3"><slot name="a"></slot></div>
471+
</div>`
472+
}
473+
}
464474
}).$mount()
465-
expect('Duplicate default <slot>').toHaveBeenWarned()
466-
expect('Duplicate <slot> with name "a"').toHaveBeenWarned()
475+
expect('Duplicate presense of slot "default"').toHaveBeenWarned()
476+
expect('Duplicate presense of slot "a"').toHaveBeenWarned()
467477
})
468478

469-
it('warn static slot inside v-for', () => {
470-
new Vue({
471-
template: `<div>
472-
<div v-for="i in 1"><slot :name="'test' + i"></slot></div>
473-
</div>`
474-
}).$mount()
475-
expect('Static <slot> found inside v-for').not.toHaveBeenWarned()
476-
479+
it('should not warn valid conditional slots', () => {
477480
new Vue({
478481
template: `<div>
479-
<div v-for="i in 1"><slot></slot></div>
480-
</div>`
482+
<test>
483+
<div>foo</div>
484+
</test>
485+
</div>`,
486+
components: {
487+
test: {
488+
template: `<div>
489+
<slot v-if="true"></slot>
490+
<slot v-else></slot>
491+
</div>`
492+
}
493+
}
481494
}).$mount()
482-
expect('Static <slot> found inside v-for').toHaveBeenWarned()
495+
expect('Duplicate presense of slot "default"').not.toHaveBeenWarned()
483496
})
484497

485498
// #3518

test/unit/modules/compiler/codegen.spec.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -98,21 +98,21 @@ describe('codegen', () => {
9898
it('generate single slot', () => {
9999
assertCodegen(
100100
'<slot></slot>',
101-
`with(this){return $slots["default"]}`
101+
`with(this){return _t("default")}`
102102
)
103103
})
104104

105105
it('generate named slot', () => {
106106
assertCodegen(
107107
'<slot name="one"></slot>',
108-
`with(this){return $slots["one"]}`
108+
`with(this){return _t("one")}`
109109
)
110110
})
111111

112112
it('generate slot fallback content', () => {
113113
assertCodegen(
114114
'<slot><div>hi</div></slot>',
115-
`with(this){return ($slots["default"]||[_m(0)])}`,
115+
`with(this){return _t("default",[_m(0)])}`,
116116
[`with(this){return _h('div',["hi"])}`]
117117
)
118118
})

0 commit comments

Comments
 (0)