Преглед изворни кода

fix(compiler-core): avoid double processing v-for keys with v-memo (#14861)

close #14859
山吹色御守 пре 2 недеља
родитељ
комит
34a0ded4d2

+ 38 - 18
packages/compiler-core/__tests__/transforms/__snapshots__/vMemo.spec.ts.snap

@@ -1,23 +1,5 @@
 // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
 // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
 
 
-exports[`compiler: v-memo transform > element v-for key expression prefixing + v-memo 1`] = `
-"import { renderList as _renderList, Fragment as _Fragment, openBlock as _openBlock, createElementBlock as _createElementBlock, isMemoSame as _isMemoSame, withMemo as _withMemo } from "vue"
-
-export function render(_ctx, _cache) {
-  return (_openBlock(), _createElementBlock("div", null, [
-    (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(_ctx.tableData, (data, __, ___, _cached) => {
-      const _memo = (_ctx.getLetter(data))
-      if (_cached && _cached.el && _cached.key === _ctx.getId(data) && _isMemoSame(_cached, _memo)) return _cached
-      const _item = (_openBlock(), _createElementBlock("span", {
-        key: _ctx.getId(data)
-      }))
-      _item.memo = _memo
-      return _item
-    }, _cache, 0), 128 /* KEYED_FRAGMENT */))
-  ]))
-}"
-`;
-
 exports[`compiler: v-memo transform > on component 1`] = `
 exports[`compiler: v-memo transform > on component 1`] = `
 "import { resolveComponent as _resolveComponent, createVNode as _createVNode, withMemo as _withMemo, openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"
 "import { resolveComponent as _resolveComponent, createVNode as _createVNode, withMemo as _withMemo, openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"
 
 
@@ -64,6 +46,24 @@ export function render(_ctx, _cache) {
 }"
 }"
 `;
 `;
 
 
+exports[`compiler: v-memo transform > on template v-for w/ compound key expression 1`] = `
+"import { renderList as _renderList, Fragment as _Fragment, openBlock as _openBlock, createElementBlock as _createElementBlock, isMemoSame as _isMemoSame, withMemo as _withMemo } from "vue"
+
+export function render(_ctx, _cache) {
+  return (_openBlock(), _createElementBlock("div", null, [
+    (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(_ctx.list, ({ x, y }, __, ___, _cached) => {
+      const _memo = ([x, y === _ctx.z])
+      if (_cached && _cached.el && _cached.key === _ctx.get(x) && _isMemoSame(_cached, _memo)) return _cached
+      const _item = (_openBlock(), _createElementBlock("span", {
+        key: _ctx.get(x)
+      }, "foobar"))
+      _item.memo = _memo
+      return _item
+    }, _cache, 0), 128 /* KEYED_FRAGMENT */))
+  ]))
+}"
+`;
+
 exports[`compiler: v-memo transform > on v-for 1`] = `
 exports[`compiler: v-memo transform > on v-for 1`] = `
 "import { renderList as _renderList, Fragment as _Fragment, openBlock as _openBlock, createElementBlock as _createElementBlock, createElementVNode as _createElementVNode, isMemoSame as _isMemoSame, withMemo as _withMemo } from "vue"
 "import { renderList as _renderList, Fragment as _Fragment, openBlock as _openBlock, createElementBlock as _createElementBlock, createElementVNode as _createElementVNode, isMemoSame as _isMemoSame, withMemo as _withMemo } from "vue"
 
 
@@ -82,6 +82,26 @@ export function render(_ctx, _cache) {
 }"
 }"
 `;
 `;
 
 
+exports[`compiler: v-memo transform > on v-for w/ compound key expression 1`] = `
+"import { renderList as _renderList, Fragment as _Fragment, openBlock as _openBlock, createElementBlock as _createElementBlock, createElementVNode as _createElementVNode, isMemoSame as _isMemoSame, withMemo as _withMemo } from "vue"
+
+export function render(_ctx, _cache) {
+  return (_openBlock(), _createElementBlock("div", null, [
+    (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(_ctx.list, ({ x, y }, __, ___, _cached) => {
+      const _memo = ([x, y === _ctx.z])
+      if (_cached && _cached.el && _cached.key === _ctx.get(x) && _isMemoSame(_cached, _memo)) return _cached
+      const _item = (_openBlock(), _createElementBlock("div", {
+        key: _ctx.get(x)
+      }, [
+        _createElementVNode("span", null, "foobar")
+      ]))
+      _item.memo = _memo
+      return _item
+    }, _cache, 0), 128 /* KEYED_FRAGMENT */))
+  ]))
+}"
+`;
+
 exports[`compiler: v-memo transform > on v-if 1`] = `
 exports[`compiler: v-memo transform > on v-if 1`] = `
 "import { createElementVNode as _createElementVNode, createTextVNode as _createTextVNode, openBlock as _openBlock, createElementBlock as _createElementBlock, withMemo as _withMemo, createCommentVNode as _createCommentVNode, resolveComponent as _resolveComponent, createBlock as _createBlock } from "vue"
 "import { createElementVNode as _createElementVNode, createTextVNode as _createTextVNode, openBlock as _openBlock, createElementBlock as _createElementBlock, withMemo as _withMemo, createCommentVNode as _createCommentVNode, resolveComponent as _resolveComponent, createBlock as _createBlock } from "vue"
 
 

+ 40 - 0
packages/compiler-core/__tests__/transforms/vFor.spec.ts

@@ -641,6 +641,26 @@ describe('compiler: v-for', () => {
       })
       })
     })
     })
 
 
+    test('element v-for key expression prefixing on simple expression', () => {
+      const {
+        node: { codegenNode },
+      } = parseWithForTransform(
+        '<div v-for="item in items" :key="itemKey">test</div>',
+        { prefixIdentifiers: true },
+      )
+      const innerBlock = codegenNode.children.arguments[1].returns
+      expect(innerBlock).toMatchObject({
+        type: NodeTypes.VNODE_CALL,
+        tag: `"div"`,
+        props: createObjectMatcher({
+          key: {
+            type: NodeTypes.SIMPLE_EXPRESSION,
+            content: `_ctx.itemKey`,
+          },
+        }),
+      })
+    })
+
     // #2085
     // #2085
     test('template v-for key expression prefixing', () => {
     test('template v-for key expression prefixing', () => {
       const {
       const {
@@ -669,6 +689,26 @@ describe('compiler: v-for', () => {
       })
       })
     })
     })
 
 
+    test('template v-for key expression prefixing on simple expression', () => {
+      const {
+        node: { codegenNode },
+      } = parseWithForTransform(
+        '<template v-for="item in items" :key="itemKey">test</template>',
+        { prefixIdentifiers: true },
+      )
+      const innerBlock = codegenNode.children.arguments[1].returns
+      expect(innerBlock).toMatchObject({
+        type: NodeTypes.VNODE_CALL,
+        tag: FRAGMENT,
+        props: createObjectMatcher({
+          key: {
+            type: NodeTypes.SIMPLE_EXPRESSION,
+            content: `_ctx.itemKey`,
+          },
+        }),
+      })
+    })
+
     test('template v-for key no prefixing on attribute key', () => {
     test('template v-for key no prefixing on attribute key', () => {
       const {
       const {
         node: { codegenNode },
         node: { codegenNode },

+ 14 - 2
packages/compiler-core/__tests__/transforms/vMemo.spec.ts

@@ -44,6 +44,16 @@ describe('compiler: v-memo transform', () => {
     ).toMatchSnapshot()
     ).toMatchSnapshot()
   })
   })
 
 
+  test('on v-for w/ compound key expression', () => {
+    expect(
+      compile(
+        `<div v-for="{ x, y } in list" :key="get(x)" v-memo="[x, y === z]">
+          <span>foobar</span>
+        </div>`,
+      ),
+    ).toMatchSnapshot()
+  })
+
   test('on template v-for', () => {
   test('on template v-for', () => {
     expect(
     expect(
       compile(
       compile(
@@ -54,10 +64,12 @@ describe('compiler: v-memo transform', () => {
     ).toMatchSnapshot()
     ).toMatchSnapshot()
   })
   })
 
 
-  test('element v-for key expression prefixing + v-memo', () => {
+  test('on template v-for w/ compound key expression', () => {
     expect(
     expect(
       compile(
       compile(
-        `<span v-for="data of tableData" :key="getId(data)" v-memo="getLetter(data)"></span>`,
+        `<template v-for="{ x, y } in list" :key="get(x)" v-memo="[x, y === z]">
+          <span>foobar</span>
+        </template>`,
       ),
       ),
     ).toMatchSnapshot()
     ).toMatchSnapshot()
   })
   })

+ 11 - 17
packages/compiler-core/src/transforms/vFor.ts

@@ -71,33 +71,27 @@ export const transformFor: NodeTransform = createStructuralDirectiveTransform(
             : undefined
             : undefined
           : keyProp.exp)
           : keyProp.exp)
 
 
-      if (memo && keyExp && isDirKey) {
-        if (!__BROWSER__) {
-          keyProp.exp = keyExp = processExpression(
-            keyExp as SimpleExpressionNode,
-            context,
-          )
-        }
-      }
-      const keyProperty =
-        keyProp && keyExp ? createObjectProperty(`key`, keyExp) : null
+      const keyProperty = keyExp ? createObjectProperty(`key`, keyExp) : null
 
 
-      if (!__BROWSER__ && isTemplate) {
+      if (!__BROWSER__) {
         // #2085 / #5288 process :key and v-memo expressions need to be
         // #2085 / #5288 process :key and v-memo expressions need to be
         // processed on `<template v-for>`. In this case the node is discarded
         // processed on `<template v-for>`. In this case the node is discarded
         // and never traversed so its binding expressions won't be processed
         // and never traversed so its binding expressions won't be processed
         // by the normal transforms.
         // by the normal transforms.
-        if (memo) {
+        if (isTemplate && memo) {
           memo.exp = processExpression(
           memo.exp = processExpression(
             memo.exp! as SimpleExpressionNode,
             memo.exp! as SimpleExpressionNode,
             context,
             context,
           )
           )
         }
         }
-        if (keyProperty && keyProp!.type !== NodeTypes.ATTRIBUTE) {
-          keyProperty.value = processExpression(
-            keyProperty.value as SimpleExpressionNode,
-            context,
-          )
+        if ((isTemplate || memo) && keyProperty && isDirKey) {
+          keyExp =
+            keyProp.exp =
+            keyProperty.value =
+              processExpression(
+                keyProperty.value as SimpleExpressionNode,
+                context,
+              )
         }
         }
       }
       }