Skip to content

Commit 00036bb

Browse files
committed
fix(slots): ensure different branches of dynamic slots have different keys
fix #6202
1 parent 96eb745 commit 00036bb

File tree

8 files changed

+69
-20
lines changed

8 files changed

+69
-20
lines changed

packages/compiler-core/__tests__/__snapshots__/scopeId.spec.ts.snap

+2-1
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,8 @@ export function render(_ctx, _cache) {
4343
name: \\"foo\\",
4444
fn: _withCtx(() => [
4545
_createElementVNode(\\"div\\")
46-
])
46+
]),
47+
key: \\"0\\"
4748
}
4849
: undefined,
4950
_renderList(_ctx.list, (i) => {

packages/compiler-core/__tests__/transforms/__snapshots__/vSlot.spec.ts.snap

+10-5
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,8 @@ return function render(_ctx, _cache) {
5656
(_ctx.ok)
5757
? {
5858
name: \\"one\\",
59-
fn: _withCtx((props) => [_toDisplayString(props)])
59+
fn: _withCtx((props) => [_toDisplayString(props)]),
60+
key: \\"0\\"
6061
}
6162
: undefined
6263
]), 1024 /* DYNAMIC_SLOTS */))
@@ -76,16 +77,19 @@ return function render(_ctx, _cache) {
7677
ok
7778
? {
7879
name: \\"one\\",
79-
fn: _withCtx(() => [\\"foo\\"])
80+
fn: _withCtx(() => [\\"foo\\"]),
81+
key: \\"0\\"
8082
}
8183
: orNot
8284
? {
8385
name: \\"two\\",
84-
fn: _withCtx((props) => [\\"bar\\"])
86+
fn: _withCtx((props) => [\\"bar\\"]),
87+
key: \\"1\\"
8588
}
8689
: {
8790
name: \\"one\\",
88-
fn: _withCtx(() => [\\"baz\\"])
91+
fn: _withCtx(() => [\\"baz\\"]),
92+
key: \\"2\\"
8993
}
9094
]), 1024 /* DYNAMIC_SLOTS */))
9195
}
@@ -105,7 +109,8 @@ return function render(_ctx, _cache) {
105109
ok
106110
? {
107111
name: \\"one\\",
108-
fn: _withCtx(() => [\\"hello\\"])
112+
fn: _withCtx(() => [\\"hello\\"]),
113+
key: \\"0\\"
109114
}
110115
: undefined
111116
]), 1024 /* DYNAMIC_SLOTS */))

packages/compiler-core/__tests__/transforms/vSlot.spec.ts

+10-5
Original file line numberDiff line numberDiff line change
@@ -568,7 +568,8 @@ describe('compiler: transform component slots', () => {
568568
fn: {
569569
type: NodeTypes.JS_FUNCTION_EXPRESSION,
570570
returns: [{ type: NodeTypes.TEXT, content: `hello` }]
571-
}
571+
},
572+
key: `0`
572573
}),
573574
alternate: {
574575
content: `undefined`,
@@ -616,7 +617,8 @@ describe('compiler: transform component slots', () => {
616617
content: { content: `props` }
617618
}
618619
]
619-
}
620+
},
621+
key: `0`
620622
}),
621623
alternate: {
622624
content: `undefined`,
@@ -660,7 +662,8 @@ describe('compiler: transform component slots', () => {
660662
type: NodeTypes.JS_FUNCTION_EXPRESSION,
661663
params: undefined,
662664
returns: [{ type: NodeTypes.TEXT, content: `foo` }]
663-
}
665+
},
666+
key: `0`
664667
}),
665668
alternate: {
666669
type: NodeTypes.JS_CONDITIONAL_EXPRESSION,
@@ -671,15 +674,17 @@ describe('compiler: transform component slots', () => {
671674
type: NodeTypes.JS_FUNCTION_EXPRESSION,
672675
params: { content: `props` },
673676
returns: [{ type: NodeTypes.TEXT, content: `bar` }]
674-
}
677+
},
678+
key: `1`
675679
}),
676680
alternate: createObjectMatcher({
677681
name: `one`,
678682
fn: {
679683
type: NodeTypes.JS_FUNCTION_EXPRESSION,
680684
params: undefined,
681685
returns: [{ type: NodeTypes.TEXT, content: `baz` }]
682-
}
686+
},
687+
key: `2`
683688
})
684689
}
685690
}

packages/compiler-core/src/transforms/vSlot.ts

+18-6
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,7 @@ export function buildSlots(
160160
let hasNamedDefaultSlot = false
161161
const implicitDefaultChildren: TemplateChildNode[] = []
162162
const seenSlotNames = new Set<string>()
163+
let conditionalBranchIndex = 0
163164

164165
for (let i = 0; i < children.length; i++) {
165166
const slotElement = children[i]
@@ -210,7 +211,7 @@ export function buildSlots(
210211
dynamicSlots.push(
211212
createConditionalExpression(
212213
vIf.exp!,
213-
buildDynamicSlot(slotName, slotFunction),
214+
buildDynamicSlot(slotName, slotFunction, conditionalBranchIndex++),
214215
defaultFallback
215216
)
216217
)
@@ -243,10 +244,14 @@ export function buildSlots(
243244
conditional.alternate = vElse.exp
244245
? createConditionalExpression(
245246
vElse.exp,
246-
buildDynamicSlot(slotName, slotFunction),
247+
buildDynamicSlot(
248+
slotName,
249+
slotFunction,
250+
conditionalBranchIndex++
251+
),
247252
defaultFallback
248253
)
249-
: buildDynamicSlot(slotName, slotFunction)
254+
: buildDynamicSlot(slotName, slotFunction, conditionalBranchIndex++)
250255
} else {
251256
context.onError(
252257
createCompilerError(ErrorCodes.X_V_ELSE_NO_ADJACENT_IF, vElse.loc)
@@ -369,12 +374,19 @@ export function buildSlots(
369374

370375
function buildDynamicSlot(
371376
name: ExpressionNode,
372-
fn: FunctionExpression
377+
fn: FunctionExpression,
378+
index?: number
373379
): ObjectExpression {
374-
return createObjectExpression([
380+
const props = [
375381
createObjectProperty(`name`, name),
376382
createObjectProperty(`fn`, fn)
377-
])
383+
]
384+
if (index != null) {
385+
props.push(
386+
createObjectProperty(`key`, createSimpleExpression(String(index), true))
387+
)
388+
}
389+
return createObjectExpression(props)
378390
}
379391

380392
function hasForwardedSlots(children: TemplateChildNode[]): boolean {

packages/compiler-ssr/__tests__/ssrComponent.spec.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,8 @@ describe('ssr: components', () => {
166166
_createTextVNode(\\"foo\\")
167167
]
168168
}
169-
})
169+
}),
170+
key: \\"0\\"
170171
}
171172
: undefined
172173
]), _parent))

packages/runtime-core/__tests__/helpers/createSlots.spec.ts

+9
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,15 @@ describe('createSlot', () => {
1717
expect(actual).toEqual({ descriptor: slot })
1818
})
1919

20+
it('should attach key', () => {
21+
const dynamicSlot = [{ name: 'descriptor', fn: slot, key: '1' }]
22+
23+
const actual = createSlots(record, dynamicSlot)
24+
const ret = actual.descriptor()
25+
// @ts-ignore
26+
expect(ret.key).toBe('1')
27+
})
28+
2029
it('should add all slots to the record', () => {
2130
const dynamicSlot = [
2231
{ name: 'descriptor', fn: slot },

packages/runtime-core/src/helpers/createSlots.ts

+10-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { isArray } from '@vue/shared'
44
interface CompiledSlotDescriptor {
55
name: string
66
fn: Slot
7+
key?: string
78
}
89

910
/**
@@ -27,7 +28,15 @@ export function createSlots(
2728
}
2829
} else if (slot) {
2930
// conditional single slot generated by <template v-if="..." #foo>
30-
slots[slot.name] = slot.fn
31+
slots[slot.name] = slot.key
32+
? (...args: any[]) => {
33+
const res = slot.fn(...args)
34+
// attach branch key so each conditional branch is considered a
35+
// different fragment
36+
;(res as any).key = slot.key
37+
return res
38+
}
39+
: slot.fn
3140
}
3241
}
3342
return slots

packages/runtime-core/src/helpers/renderSlot.ts

+8-1
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,14 @@ export function renderSlot(
6666
const validSlotContent = slot && ensureValidVNode(slot(props))
6767
const rendered = createBlock(
6868
Fragment,
69-
{ key: props.key || `_${name}` },
69+
{
70+
key:
71+
props.key ||
72+
// slot content array of a dynamic conditional slot may have a branch
73+
// key attached in the `createSlots` helper, respect that
74+
(validSlotContent && (validSlotContent as any).key) ||
75+
`_${name}`
76+
},
7077
validSlotContent || (fallback ? fallback() : []),
7178
validSlotContent && (slots as RawSlots)._ === SlotFlags.STABLE
7279
? PatchFlags.STABLE_FRAGMENT

0 commit comments

Comments
 (0)