dx(compiler-core): warn on <template v-for>
key misplacement
Note: the behavior is different from Vue 2. `<template v-for>` are compiled into an array of Fragment vnodes so the key should be placed the `<template>` for v-for to use it for diffing.
This commit is contained in:
parent
de0c8a7e3e
commit
b0d01e9db9
@ -263,6 +263,34 @@ describe('compiler: v-for', () => {
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
test('<template v-for> key placement', () => {
|
||||
const onError = jest.fn()
|
||||
parseWithForTransform(
|
||||
`
|
||||
<template v-for="item in items">
|
||||
<div :key="item.id"/>
|
||||
</template>`,
|
||||
{ onError }
|
||||
)
|
||||
|
||||
expect(onError).toHaveBeenCalledTimes(1)
|
||||
expect(onError).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
code: ErrorCodes.X_V_FOR_TEMPLATE_KEY_PLACEMENT
|
||||
})
|
||||
)
|
||||
|
||||
// should not warn on nested v-for keys
|
||||
parseWithForTransform(
|
||||
`
|
||||
<template v-for="item in items">
|
||||
<div v-for="c in item.children" :key="c.id"/>
|
||||
</template>`,
|
||||
{ onError }
|
||||
)
|
||||
expect(onError).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
})
|
||||
|
||||
describe('source location', () => {
|
||||
|
@ -67,6 +67,7 @@ export const enum ErrorCodes {
|
||||
X_V_ELSE_NO_ADJACENT_IF,
|
||||
X_V_FOR_NO_EXPRESSION,
|
||||
X_V_FOR_MALFORMED_EXPRESSION,
|
||||
X_V_FOR_TEMPLATE_KEY_PLACEMENT,
|
||||
X_V_BIND_NO_EXPRESSION,
|
||||
X_V_ON_NO_EXPRESSION,
|
||||
X_V_SLOT_UNEXPECTED_DIRECTIVE_ON_SLOT_OUTLET,
|
||||
@ -140,6 +141,7 @@ export const errorMessages: { [code: number]: string } = {
|
||||
[ErrorCodes.X_V_ELSE_NO_ADJACENT_IF]: `v-else/v-else-if has no adjacent v-if.`,
|
||||
[ErrorCodes.X_V_FOR_NO_EXPRESSION]: `v-for is missing expression.`,
|
||||
[ErrorCodes.X_V_FOR_MALFORMED_EXPRESSION]: `v-for has invalid expression.`,
|
||||
[ErrorCodes.X_V_FOR_TEMPLATE_KEY_PLACEMENT]: `<template v-for> key should be placed on the <template> tag.`,
|
||||
[ErrorCodes.X_V_BIND_NO_EXPRESSION]: `v-bind is missing expression.`,
|
||||
[ErrorCodes.X_V_ON_NO_EXPRESSION]: `v-on is missing expression.`,
|
||||
[ErrorCodes.X_V_SLOT_UNEXPECTED_DIRECTIVE_ON_SLOT_OUTLET]: `Unexpected custom directive on <slot> outlet.`,
|
||||
|
@ -10,7 +10,6 @@ import {
|
||||
SimpleExpressionNode,
|
||||
createCallExpression,
|
||||
createFunctionExpression,
|
||||
ElementTypes,
|
||||
createObjectExpression,
|
||||
createObjectProperty,
|
||||
ForCodegenNode,
|
||||
@ -81,6 +80,25 @@ export const transformFor = createStructuralDirectiveTransform(
|
||||
let childBlock: BlockCodegenNode
|
||||
const isTemplate = isTemplateNode(node)
|
||||
const { children } = forNode
|
||||
|
||||
// check <template v-for> key placement
|
||||
if ((__DEV__ || !__BROWSER__) && isTemplate) {
|
||||
node.children.some(c => {
|
||||
if (c.type === NodeTypes.ELEMENT) {
|
||||
const key = findProp(c, 'key')
|
||||
if (key) {
|
||||
context.onError(
|
||||
createCompilerError(
|
||||
ErrorCodes.X_V_FOR_TEMPLATE_KEY_PLACEMENT,
|
||||
key.loc
|
||||
)
|
||||
)
|
||||
return true
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const needFragmentWrapper =
|
||||
children.length !== 1 || children[0].type !== NodeTypes.ELEMENT
|
||||
const slotOutlet = isSlotOutlet(node)
|
||||
@ -183,7 +201,7 @@ export function processFor(
|
||||
keyAlias: key,
|
||||
objectIndexAlias: index,
|
||||
parseResult,
|
||||
children: node.tagType === ElementTypes.TEMPLATE ? node.children : [node]
|
||||
children: isTemplateNode(node) ? node.children : [node]
|
||||
}
|
||||
|
||||
context.replaceNode(forNode)
|
||||
|
Loading…
x
Reference in New Issue
Block a user