]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
fix(compiler-core): handle v-memo + v-for with functional key (#12014)
authorlinzhe <40790268+linzhe141@users.noreply.github.com>
Thu, 14 Nov 2024 07:14:29 +0000 (15:14 +0800)
committerGitHub <noreply@github.com>
Thu, 14 Nov 2024 07:14:29 +0000 (15:14 +0800)
close #12013

packages/compiler-core/__tests__/transforms/__snapshots__/vMemo.spec.ts.snap
packages/compiler-core/__tests__/transforms/vMemo.spec.ts
packages/compiler-core/src/transforms/transformExpression.ts
packages/compiler-core/src/transforms/vFor.ts

index 220bc177418c1ae2067449b94066487d1195309a..86e0b3d2fd5666afee033230cc382e035370db30 100644 (file)
@@ -1,5 +1,23 @@
 // 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.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`] = `
 "import { resolveComponent as _resolveComponent, createVNode as _createVNode, withMemo as _withMemo, openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"
 
index 85769e6e97735da5b8f6198b96e28d9bf93aff14..41e7d922ebe37d59979bcf6f9744a3a0696d13fa 100644 (file)
@@ -53,4 +53,12 @@ describe('compiler: v-memo transform', () => {
       ),
     ).toMatchSnapshot()
   })
+
+  test('element v-for key expression prefixing + v-memo', () => {
+    expect(
+      compile(
+        `<span v-for="data of tableData" :key="getId(data)" v-memo="getLetter(data)"></span>`,
+      ),
+    ).toMatchSnapshot()
+  })
 })
index ec2d4685314c4648757611e775d6fc13ffd58108..9ae8897e674866e821dc75d56d608fd5a0ec29cb 100644 (file)
@@ -24,7 +24,7 @@ import {
   isStaticPropertyKey,
   walkIdentifiers,
 } from '../babelUtils'
-import { advancePositionWithClone, isSimpleIdentifier } from '../utils'
+import { advancePositionWithClone, findDir, isSimpleIdentifier } from '../utils'
 import {
   genPropsAccessExp,
   hasOwn,
@@ -54,6 +54,7 @@ export const transformExpression: NodeTransform = (node, context) => {
     )
   } else if (node.type === NodeTypes.ELEMENT) {
     // handle directives on element
+    const memo = findDir(node, 'memo')
     for (let i = 0; i < node.props.length; i++) {
       const dir = node.props[i]
       // do not process for v-on & v-for since they are special handled
@@ -65,7 +66,14 @@ export const transformExpression: NodeTransform = (node, context) => {
         if (
           exp &&
           exp.type === NodeTypes.SIMPLE_EXPRESSION &&
-          !(dir.name === 'on' && arg)
+          !(dir.name === 'on' && arg) &&
+          // key has been processed in transformFor(vMemo + vFor)
+          !(
+            memo &&
+            arg &&
+            arg.type === NodeTypes.SIMPLE_EXPRESSION &&
+            arg.content === 'key'
+          )
         ) {
           dir.exp = processExpression(
             exp,
index ec1c21ff88b833c2d9aab96a34218958e8008bdb..0dca0ba9ab437216347a353dcabcec01d44fc07b 100644 (file)
@@ -63,17 +63,27 @@ export const transformFor: NodeTransform = createStructuralDirectiveTransform(
       const isTemplate = isTemplateNode(node)
       const memo = findDir(node, 'memo')
       const keyProp = findProp(node, `key`, false, true)
-      if (keyProp && keyProp.type === NodeTypes.DIRECTIVE && !keyProp.exp) {
+      const isDirKey = keyProp && keyProp.type === NodeTypes.DIRECTIVE
+      if (isDirKey && !keyProp.exp) {
         // resolve :key shorthand #10882
         transformBindShorthand(keyProp, context)
       }
-      const keyExp =
+      let keyExp =
         keyProp &&
         (keyProp.type === NodeTypes.ATTRIBUTE
           ? keyProp.value
             ? createSimpleExpression(keyProp.value.content, true)
             : undefined
           : keyProp.exp)
+
+      if (memo && keyExp && isDirKey) {
+        if (!__BROWSER__) {
+          keyProp.exp = keyExp = processExpression(
+            keyExp as SimpleExpressionNode,
+            context,
+          )
+        }
+      }
       const keyProperty =
         keyProp && keyExp ? createObjectProperty(`key`, keyExp) : null