]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
dx(compiler-core): warn on `<template v-for>` key misplacement
authorEvan You <yyx990803@gmail.com>
Tue, 4 Aug 2020 16:20:32 +0000 (12:20 -0400)
committerEvan You <yyx990803@gmail.com>
Tue, 4 Aug 2020 16:20:32 +0000 (12:20 -0400)
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.

packages/compiler-core/__tests__/transforms/vFor.spec.ts
packages/compiler-core/src/errors.ts
packages/compiler-core/src/transforms/vFor.ts

index f3168bb53cf820d3d967f5d7fda3164d54a97f97..b8d6b3b3901c8b08e86e75cf6388a5ff082f6389 100644 (file)
@@ -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', () => {
index 28c603a8ab86cc094ba60b1c25752f1168cb75aa..a3419c88455f97b0790764f3ef5116ad9e87dbae 100644 (file)
@@ -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.`,
index 68fd3f4553400cd246fd2443fe68c3dfb40d0941..8ce821f972a903f182021f481ddbd5056e3ed236 100644 (file)
@@ -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)