fix(slots): ensure different branches of dynamic slots have different keys
fix #6202
This commit is contained in:
parent
96eb745254
commit
00036bb52c
@ -43,7 +43,8 @@ export function render(_ctx, _cache) {
|
|||||||
name: \\"foo\\",
|
name: \\"foo\\",
|
||||||
fn: _withCtx(() => [
|
fn: _withCtx(() => [
|
||||||
_createElementVNode(\\"div\\")
|
_createElementVNode(\\"div\\")
|
||||||
])
|
]),
|
||||||
|
key: \\"0\\"
|
||||||
}
|
}
|
||||||
: undefined,
|
: undefined,
|
||||||
_renderList(_ctx.list, (i) => {
|
_renderList(_ctx.list, (i) => {
|
||||||
|
@ -56,7 +56,8 @@ return function render(_ctx, _cache) {
|
|||||||
(_ctx.ok)
|
(_ctx.ok)
|
||||||
? {
|
? {
|
||||||
name: \\"one\\",
|
name: \\"one\\",
|
||||||
fn: _withCtx((props) => [_toDisplayString(props)])
|
fn: _withCtx((props) => [_toDisplayString(props)]),
|
||||||
|
key: \\"0\\"
|
||||||
}
|
}
|
||||||
: undefined
|
: undefined
|
||||||
]), 1024 /* DYNAMIC_SLOTS */))
|
]), 1024 /* DYNAMIC_SLOTS */))
|
||||||
@ -76,16 +77,19 @@ return function render(_ctx, _cache) {
|
|||||||
ok
|
ok
|
||||||
? {
|
? {
|
||||||
name: \\"one\\",
|
name: \\"one\\",
|
||||||
fn: _withCtx(() => [\\"foo\\"])
|
fn: _withCtx(() => [\\"foo\\"]),
|
||||||
|
key: \\"0\\"
|
||||||
}
|
}
|
||||||
: orNot
|
: orNot
|
||||||
? {
|
? {
|
||||||
name: \\"two\\",
|
name: \\"two\\",
|
||||||
fn: _withCtx((props) => [\\"bar\\"])
|
fn: _withCtx((props) => [\\"bar\\"]),
|
||||||
|
key: \\"1\\"
|
||||||
}
|
}
|
||||||
: {
|
: {
|
||||||
name: \\"one\\",
|
name: \\"one\\",
|
||||||
fn: _withCtx(() => [\\"baz\\"])
|
fn: _withCtx(() => [\\"baz\\"]),
|
||||||
|
key: \\"2\\"
|
||||||
}
|
}
|
||||||
]), 1024 /* DYNAMIC_SLOTS */))
|
]), 1024 /* DYNAMIC_SLOTS */))
|
||||||
}
|
}
|
||||||
@ -105,7 +109,8 @@ return function render(_ctx, _cache) {
|
|||||||
ok
|
ok
|
||||||
? {
|
? {
|
||||||
name: \\"one\\",
|
name: \\"one\\",
|
||||||
fn: _withCtx(() => [\\"hello\\"])
|
fn: _withCtx(() => [\\"hello\\"]),
|
||||||
|
key: \\"0\\"
|
||||||
}
|
}
|
||||||
: undefined
|
: undefined
|
||||||
]), 1024 /* DYNAMIC_SLOTS */))
|
]), 1024 /* DYNAMIC_SLOTS */))
|
||||||
|
@ -568,7 +568,8 @@ describe('compiler: transform component slots', () => {
|
|||||||
fn: {
|
fn: {
|
||||||
type: NodeTypes.JS_FUNCTION_EXPRESSION,
|
type: NodeTypes.JS_FUNCTION_EXPRESSION,
|
||||||
returns: [{ type: NodeTypes.TEXT, content: `hello` }]
|
returns: [{ type: NodeTypes.TEXT, content: `hello` }]
|
||||||
}
|
},
|
||||||
|
key: `0`
|
||||||
}),
|
}),
|
||||||
alternate: {
|
alternate: {
|
||||||
content: `undefined`,
|
content: `undefined`,
|
||||||
@ -616,7 +617,8 @@ describe('compiler: transform component slots', () => {
|
|||||||
content: { content: `props` }
|
content: { content: `props` }
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
},
|
||||||
|
key: `0`
|
||||||
}),
|
}),
|
||||||
alternate: {
|
alternate: {
|
||||||
content: `undefined`,
|
content: `undefined`,
|
||||||
@ -660,7 +662,8 @@ describe('compiler: transform component slots', () => {
|
|||||||
type: NodeTypes.JS_FUNCTION_EXPRESSION,
|
type: NodeTypes.JS_FUNCTION_EXPRESSION,
|
||||||
params: undefined,
|
params: undefined,
|
||||||
returns: [{ type: NodeTypes.TEXT, content: `foo` }]
|
returns: [{ type: NodeTypes.TEXT, content: `foo` }]
|
||||||
}
|
},
|
||||||
|
key: `0`
|
||||||
}),
|
}),
|
||||||
alternate: {
|
alternate: {
|
||||||
type: NodeTypes.JS_CONDITIONAL_EXPRESSION,
|
type: NodeTypes.JS_CONDITIONAL_EXPRESSION,
|
||||||
@ -671,7 +674,8 @@ describe('compiler: transform component slots', () => {
|
|||||||
type: NodeTypes.JS_FUNCTION_EXPRESSION,
|
type: NodeTypes.JS_FUNCTION_EXPRESSION,
|
||||||
params: { content: `props` },
|
params: { content: `props` },
|
||||||
returns: [{ type: NodeTypes.TEXT, content: `bar` }]
|
returns: [{ type: NodeTypes.TEXT, content: `bar` }]
|
||||||
}
|
},
|
||||||
|
key: `1`
|
||||||
}),
|
}),
|
||||||
alternate: createObjectMatcher({
|
alternate: createObjectMatcher({
|
||||||
name: `one`,
|
name: `one`,
|
||||||
@ -679,7 +683,8 @@ describe('compiler: transform component slots', () => {
|
|||||||
type: NodeTypes.JS_FUNCTION_EXPRESSION,
|
type: NodeTypes.JS_FUNCTION_EXPRESSION,
|
||||||
params: undefined,
|
params: undefined,
|
||||||
returns: [{ type: NodeTypes.TEXT, content: `baz` }]
|
returns: [{ type: NodeTypes.TEXT, content: `baz` }]
|
||||||
}
|
},
|
||||||
|
key: `2`
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -160,6 +160,7 @@ export function buildSlots(
|
|||||||
let hasNamedDefaultSlot = false
|
let hasNamedDefaultSlot = false
|
||||||
const implicitDefaultChildren: TemplateChildNode[] = []
|
const implicitDefaultChildren: TemplateChildNode[] = []
|
||||||
const seenSlotNames = new Set<string>()
|
const seenSlotNames = new Set<string>()
|
||||||
|
let conditionalBranchIndex = 0
|
||||||
|
|
||||||
for (let i = 0; i < children.length; i++) {
|
for (let i = 0; i < children.length; i++) {
|
||||||
const slotElement = children[i]
|
const slotElement = children[i]
|
||||||
@ -210,7 +211,7 @@ export function buildSlots(
|
|||||||
dynamicSlots.push(
|
dynamicSlots.push(
|
||||||
createConditionalExpression(
|
createConditionalExpression(
|
||||||
vIf.exp!,
|
vIf.exp!,
|
||||||
buildDynamicSlot(slotName, slotFunction),
|
buildDynamicSlot(slotName, slotFunction, conditionalBranchIndex++),
|
||||||
defaultFallback
|
defaultFallback
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@ -243,10 +244,14 @@ export function buildSlots(
|
|||||||
conditional.alternate = vElse.exp
|
conditional.alternate = vElse.exp
|
||||||
? createConditionalExpression(
|
? createConditionalExpression(
|
||||||
vElse.exp,
|
vElse.exp,
|
||||||
buildDynamicSlot(slotName, slotFunction),
|
buildDynamicSlot(
|
||||||
|
slotName,
|
||||||
|
slotFunction,
|
||||||
|
conditionalBranchIndex++
|
||||||
|
),
|
||||||
defaultFallback
|
defaultFallback
|
||||||
)
|
)
|
||||||
: buildDynamicSlot(slotName, slotFunction)
|
: buildDynamicSlot(slotName, slotFunction, conditionalBranchIndex++)
|
||||||
} else {
|
} else {
|
||||||
context.onError(
|
context.onError(
|
||||||
createCompilerError(ErrorCodes.X_V_ELSE_NO_ADJACENT_IF, vElse.loc)
|
createCompilerError(ErrorCodes.X_V_ELSE_NO_ADJACENT_IF, vElse.loc)
|
||||||
@ -369,12 +374,19 @@ export function buildSlots(
|
|||||||
|
|
||||||
function buildDynamicSlot(
|
function buildDynamicSlot(
|
||||||
name: ExpressionNode,
|
name: ExpressionNode,
|
||||||
fn: FunctionExpression
|
fn: FunctionExpression,
|
||||||
|
index?: number
|
||||||
): ObjectExpression {
|
): ObjectExpression {
|
||||||
return createObjectExpression([
|
const props = [
|
||||||
createObjectProperty(`name`, name),
|
createObjectProperty(`name`, name),
|
||||||
createObjectProperty(`fn`, fn)
|
createObjectProperty(`fn`, fn)
|
||||||
])
|
]
|
||||||
|
if (index != null) {
|
||||||
|
props.push(
|
||||||
|
createObjectProperty(`key`, createSimpleExpression(String(index), true))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return createObjectExpression(props)
|
||||||
}
|
}
|
||||||
|
|
||||||
function hasForwardedSlots(children: TemplateChildNode[]): boolean {
|
function hasForwardedSlots(children: TemplateChildNode[]): boolean {
|
||||||
|
@ -166,7 +166,8 @@ describe('ssr: components', () => {
|
|||||||
_createTextVNode(\\"foo\\")
|
_createTextVNode(\\"foo\\")
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
})
|
}),
|
||||||
|
key: \\"0\\"
|
||||||
}
|
}
|
||||||
: undefined
|
: undefined
|
||||||
]), _parent))
|
]), _parent))
|
||||||
|
@ -17,6 +17,15 @@ describe('createSlot', () => {
|
|||||||
expect(actual).toEqual({ descriptor: slot })
|
expect(actual).toEqual({ descriptor: slot })
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('should attach key', () => {
|
||||||
|
const dynamicSlot = [{ name: 'descriptor', fn: slot, key: '1' }]
|
||||||
|
|
||||||
|
const actual = createSlots(record, dynamicSlot)
|
||||||
|
const ret = actual.descriptor()
|
||||||
|
// @ts-ignore
|
||||||
|
expect(ret.key).toBe('1')
|
||||||
|
})
|
||||||
|
|
||||||
it('should add all slots to the record', () => {
|
it('should add all slots to the record', () => {
|
||||||
const dynamicSlot = [
|
const dynamicSlot = [
|
||||||
{ name: 'descriptor', fn: slot },
|
{ name: 'descriptor', fn: slot },
|
||||||
|
@ -4,6 +4,7 @@ import { isArray } from '@vue/shared'
|
|||||||
interface CompiledSlotDescriptor {
|
interface CompiledSlotDescriptor {
|
||||||
name: string
|
name: string
|
||||||
fn: Slot
|
fn: Slot
|
||||||
|
key?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -27,7 +28,15 @@ export function createSlots(
|
|||||||
}
|
}
|
||||||
} else if (slot) {
|
} else if (slot) {
|
||||||
// conditional single slot generated by <template v-if="..." #foo>
|
// conditional single slot generated by <template v-if="..." #foo>
|
||||||
slots[slot.name] = slot.fn
|
slots[slot.name] = slot.key
|
||||||
|
? (...args: any[]) => {
|
||||||
|
const res = slot.fn(...args)
|
||||||
|
// attach branch key so each conditional branch is considered a
|
||||||
|
// different fragment
|
||||||
|
;(res as any).key = slot.key
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
: slot.fn
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return slots
|
return slots
|
||||||
|
@ -66,7 +66,14 @@ export function renderSlot(
|
|||||||
const validSlotContent = slot && ensureValidVNode(slot(props))
|
const validSlotContent = slot && ensureValidVNode(slot(props))
|
||||||
const rendered = createBlock(
|
const rendered = createBlock(
|
||||||
Fragment,
|
Fragment,
|
||||||
{ key: props.key || `_${name}` },
|
{
|
||||||
|
key:
|
||||||
|
props.key ||
|
||||||
|
// slot content array of a dynamic conditional slot may have a branch
|
||||||
|
// key attached in the `createSlots` helper, respect that
|
||||||
|
(validSlotContent && (validSlotContent as any).key) ||
|
||||||
|
`_${name}`
|
||||||
|
},
|
||||||
validSlotContent || (fallback ? fallback() : []),
|
validSlotContent || (fallback ? fallback() : []),
|
||||||
validSlotContent && (slots as RawSlots)._ === SlotFlags.STABLE
|
validSlotContent && (slots as RawSlots)._ === SlotFlags.STABLE
|
||||||
? PatchFlags.STABLE_FRAGMENT
|
? PatchFlags.STABLE_FRAGMENT
|
||||||
|
Loading…
Reference in New Issue
Block a user