Просмотр исходного кода

fix(vapor): v-model and v-model:model co-usage (#13070)

zhiyuanzmj 5 месяцев назад
Родитель
Сommit
bf2d2b2fde

+ 18 - 0
packages/compiler-core/__tests__/transforms/vModel.spec.ts

@@ -507,6 +507,24 @@ describe('compiler: transform v-model', () => {
     )
   })
 
+  test('should generate modelModifiers$ for component v-model:model with arguments', () => {
+    const root = parseWithVModel('<Comp v-model:model.trim="foo" />', {
+      prefixIdentifiers: true,
+    })
+    const vnodeCall = (root.children[0] as ComponentNode)
+      .codegenNode as VNodeCall
+    expect(vnodeCall.props).toMatchObject({
+      properties: [
+        { key: { content: `model` } },
+        { key: { content: `onUpdate:model` } },
+        {
+          key: { content: 'modelModifiers$' },
+          value: { content: `{ trim: true }`, isStatic: false },
+        },
+      ],
+    })
+  })
+
   describe('errors', () => {
     test('missing expression', () => {
       const onError = vi.fn()

+ 2 - 2
packages/compiler-core/src/transforms/vModel.ts

@@ -18,7 +18,7 @@ import {
 } from '../utils'
 import { IS_REF } from '../runtimeHelpers'
 import { BindingTypes } from '../options'
-import { camelize } from '@vue/shared'
+import { camelize, getModifierPropName } from '@vue/shared'
 
 export const transformModel: DirectiveTransform = (dir, node, context) => {
   const { exp, arg } = dir
@@ -136,7 +136,7 @@ export const transformModel: DirectiveTransform = (dir, node, context) => {
       .join(`, `)
     const modifiersKey = arg
       ? isStaticExp(arg)
-        ? `${arg.content}Modifiers`
+        ? getModifierPropName(arg.content)
         : createCompoundExpression([arg, ' + "Modifiers"'])
       : `modelModifiers`
     props.push(

+ 2 - 3
packages/compiler-sfc/src/script/defineModel.ts

@@ -3,6 +3,7 @@ import type { ScriptCompileContext } from './context'
 import { inferRuntimeType } from './resolveType'
 import { UNKNOWN_TYPE, isCallOf, toRuntimeTypeString } from './utils'
 import { BindingTypes, unwrapTSNode } from '@vue/compiler-dom'
+import { getModifierPropName } from '@vue/shared'
 
 export const DEFINE_MODEL = 'defineModel'
 
@@ -167,9 +168,7 @@ export function genModelProps(ctx: ScriptCompileContext): string | undefined {
     modelPropsDecl += `\n    ${JSON.stringify(name)}: ${decl},`
 
     // also generate modifiers prop
-    const modifierPropName = JSON.stringify(
-      name === 'modelValue' ? `modelModifiers` : `${name}Modifiers`,
-    )
+    const modifierPropName = JSON.stringify(getModifierPropName(name))
     modelPropsDecl += `\n    ${modifierPropName}: {},`
   }
   return `{${modelPropsDecl}\n  }`

+ 1 - 1
packages/compiler-vapor/__tests__/transforms/__snapshots__/transformText.spec.ts.snap

@@ -17,7 +17,7 @@ const t0 = _template("<div>2 foo1 1 1 1</div>", true)
 
 export function render(_ctx) {
   const n1 = t0()
-  const n0 = _child(n1)
+  const n0 = _child(n1, 0)
   return n1
 }"
 `;

+ 12 - 0
packages/compiler-vapor/__tests__/transforms/__snapshots__/vModel.spec.ts.snap

@@ -81,6 +81,18 @@ export function render(_ctx) {
 }"
 `;
 
+exports[`compiler: vModel transform > component > v-model:model with arguments for component should generate modelModifiers$ 1`] = `
+"import { resolveComponent as _resolveComponent, createComponentWithFallback as _createComponentWithFallback } from 'vue';
+
+export function render(_ctx) {
+  const _component_Comp = _resolveComponent("Comp")
+  const n0 = _createComponentWithFallback(_component_Comp, { model: () => (_ctx.foo),
+  "onUpdate:model": () => _value => (_ctx.foo = _value),
+  modelModifiers$: () => ({ trim: true }) }, null, true)
+  return n0
+}"
+`;
+
 exports[`compiler: vModel transform > modifiers > .lazy 1`] = `
 "import { applyTextModel as _applyTextModel, template as _template } from 'vue';
 const t0 = _template("<input>", true)

+ 22 - 0
packages/compiler-vapor/__tests__/transforms/vModel.spec.ts

@@ -319,6 +319,28 @@ describe('compiler: vModel transform', () => {
       })
     })
 
+    test('v-model:model with arguments for component should generate modelModifiers$', () => {
+      const { code, ir } = compileWithVModel(
+        '<Comp v-model:model.trim="foo" />',
+      )
+      expect(code).toMatchSnapshot()
+      expect(code).contain(`modelModifiers$: () => ({ trim: true })`)
+      expect(ir.block.dynamic.children[0].operation).toMatchObject({
+        type: IRNodeTypes.CREATE_COMPONENT_NODE,
+        tag: 'Comp',
+        props: [
+          [
+            {
+              key: { content: 'model', isStatic: true },
+              values: [{ content: 'foo', isStatic: false }],
+              model: true,
+              modelModifiers: ['trim'],
+            },
+          ],
+        ],
+      })
+    })
+
     test('v-model with dynamic arguments for component should generate modelModifiers ', () => {
       const { code, ir } = compileWithVModel(
         '<Comp v-model:[foo].trim="foo" v-model:[bar].number="bar" />',

+ 2 - 4
packages/compiler-vapor/src/generators/component.ts

@@ -1,4 +1,4 @@
-import { camelize, extend, isArray } from '@vue/shared'
+import { camelize, extend, getModifierPropName, isArray } from '@vue/shared'
 import type { CodegenContext } from '../generate'
 import {
   type CreateComponentIRNode,
@@ -257,9 +257,7 @@ function genModelModifiers(
   if (!modelModifiers || !modelModifiers.length) return []
 
   const modifiersKey = key.isStatic
-    ? key.content === 'modelValue'
-      ? [`modelModifiers`]
-      : [`${key.content}Modifiers`]
+    ? [getModifierPropName(key.content)]
     : ['[', ...genExpression(key, context), ' + "Modifiers"]']
 
   const modifiersVal = genDirectiveModifiers(modelModifiers)

+ 12 - 6
packages/runtime-core/src/helpers/useModel.ts

@@ -1,5 +1,11 @@
 import { type Ref, customRef, ref } from '@vue/reactivity'
-import { EMPTY_OBJ, camelize, hasChanged, hyphenate } from '@vue/shared'
+import {
+  EMPTY_OBJ,
+  camelize,
+  getModifierPropName,
+  hasChanged,
+  hyphenate,
+} from '@vue/shared'
 import type { DefineModelOptions, ModelRef } from '../apiSetupHelpers'
 import {
   type ComponentInternalInstance,
@@ -145,9 +151,9 @@ export const getModelModifiers = (
   modelName: string,
   getter: (props: Record<string, any>, key: string) => any,
 ): Record<string, boolean> | undefined => {
-  return modelName === 'modelValue' || modelName === 'model-value'
-    ? getter(props, 'modelModifiers')
-    : getter(props, `${modelName}Modifiers`) ||
-        getter(props, `${camelize(modelName)}Modifiers`) ||
-        getter(props, `${hyphenate(modelName)}Modifiers`)
+  return (
+    getter(props, getModifierPropName(modelName)) ||
+    getter(props, `${camelize(modelName)}Modifiers`) ||
+    getter(props, `${hyphenate(modelName)}Modifiers`)
+  )
 }

+ 12 - 0
packages/shared/src/general.ts

@@ -153,6 +153,18 @@ export const toHandlerKey: <T extends string>(
   },
 )
 
+/**
+ * #13070 When v-model and v-model:model directives are used together,
+ * they will generate the same modelModifiers prop,
+ * so a `$` suffix is added to avoid conflicts.
+ * @private
+ */
+export const getModifierPropName = (name: string): string => {
+  return `${
+    name === 'modelValue' || name === 'model-value' ? 'model' : name
+  }Modifiers${name === 'model' ? '$' : ''}`
+}
+
 // compare whether a value has changed, accounting for NaN.
 export const hasChanged = (value: any, oldValue: any): boolean =>
   !Object.is(value, oldValue)