什么!vue3.4开始,v-model不能用在prop上

严格来说,Vue 3 并没有在某个版本中彻底"禁止"在 prop 上使用 v-model,而是从 Vue 3.4 版本开始,改变了推荐的实现方式,并加强了对旧有错误用法的限制。

这一变化主要与新的编译宏 defineModel 的引入有关。

✨ Vue 3.4 之前的传统写法

在 Vue 3.4 之前,要在自定义组件上实现 v-model,开发者需要手动完成以下两步:

  1. 声明一个名为 modelValue 的 prop(或使用参数的自定义名称,如 title)。
  2. 在需要更新值时,通过 emit('update:modelValue', newValue) 触发事件。

这是一种"语法糖",虽然有效,但需要编写不少样板代码。

🚀 Vue 3.4 及之后的新变化

Vue 3.4 版本开始,Vue 官方引入了 defineModel 宏,旨在简化这一过程。

  • 新的推荐方式 :使用 defineModel()。它会自动帮你完成 prop 的声明和 update:modelValue 事件的触发,无需手动编写。这让代码更简洁,但也意味着旧的、手动的写法不再是官方推荐的主流。
  • 报错原因 :如果你在 Vue 3.4+ 的项目中,仍然沿用旧的思维,试图直接在一个通过 defineProps 声明的 prop 上使用 v-model(例如 v-model="modelValue"),Vue 的编译器现在会抛出一个错误: v-model cannot be used on a prop, because local prop bindings are not writable

    这是因为 prop 是单向数据流,不可直接修改。新版本更希望你使用 defineModel 来获得一个可写的响应式引用。

总结
  • Vue 3.4 之前:必须手动声明 prop 和 emit。
  • Vue 3.4 及之后 :推荐使用 defineModel 宏。继续使用旧的手动方式可能会因为直接修改 prop 而导致报错。

因此,你遇到的问题很可能是项目升级到 Vue 3.4+ 后,代码未同步更新为新规范导致的。


defineModel怎么用?

defineModel 是 Vue 3.4+ 版本引入的一个非常实用的宏(Macro),它能极大地简化父子组件之间的双向绑定逻辑。

简单来说,它把过去需要通过 definePropsdefineEmits 配合才能实现的双向绑定,封装成了一行代码。

以下是 defineModel 的具体使用方法,从基础到进阶:

1. 基础用法:默认模型

这是最简单的场景,用于替代传统的 v-model

  • 子组件 (Child.vue):

    只需调用 defineModel(),它会返回一个响应式引用,该引用自动与父组件的值同步。

    复制代码
    <script setup>
    // 1. 调用 defineModel,无需导入
    const modelValue = defineModel()
    
    // 2. 直接在模板中使用,就像使用 ref 一样
    // 修改它会自动触发 update:modelValue 事件
    </script>
    
    <template>
      <!-- 直接双向绑定 -->
      <input v-model="modelValue" placeholder="输入内容..." />
      
      <!-- 或者在 JS 中修改 -->
      <!-- modelValue.value = '新值' -->
    </template>
  • 父组件 (Parent.vue):

    使用方式与传统 v-model 完全一致,无需改变。

    复制代码
    <script setup>
    import { ref } from 'vue'
    import Child from './Child.vue'
    
    const inputValue = ref('')
    </script>
    
    <template>
      <!-- 绑定数据 -->
      <Child v-model="inputValue" />
      <p>当前值: {{ inputValue }}</p>
    </template>

2. 进阶用法:自定义名称

如果你需要多个 v-model,或者想给模型起个特定的名字(比如 username),可以传入一个字符串参数。

  • 子组件 (UserProfile.vue):

    复制代码
    <script setup>
    // 定义两个不同的模型
    const username = defineModel('username')
    const age = defineModel('age')
    </script>
    
    <template>
      <input v-model="username" placeholder="用户名" />
      <input v-model.number="age" type="number" placeholder="年龄" />
    </template>
  • 父组件:

    复制代码
    <script setup>
    import { ref } from 'vue'
    import UserProfile from './UserProfile.vue'
    
    const myName = ref('')
    const myAge = ref(0)
    </script>
    
    <template>
      <!-- 使用 v-model:名称 的形式 -->
      <UserProfile 
        v-model:username="myName" 
        v-model:age="myAge" 
      />
    </template>

3. 高级配置:类型、默认值与校验

defineModel 还支持传入配置项,这在 TypeScript 项目中非常有用。

  • 设置类型 (TypeScript):

    复制代码
    // 指定类型为 string
    const text = defineModel<string>()
    
    // 指定为必填,这样类型推导会更精确(非 undefined)
    const requiredText = defineModel<string>({ required: true })
  • 设置默认值:

    复制代码
    // 如果父组件没传值,默认使用 'hello'
    const message = defineModel({ default: 'hello' })

    注意:如果设置了默认值,建议父组件也初始化对应的 ref,以避免初始渲染时的值不同步问题。

  • 设置校验规则:

    复制代码
    const phone = defineModel('phone', {
      type: String,
      required: true,
      validator: (value) => /^1[3-9]\d{9}$/.test(value) // 简单手机号校验
    })

4. 处理修饰符 (.trim, .number 等)

defineModel 还能感知父组件传递的修饰符,比如 v-model.trim

  • 子组件 (InputWithModifiers.vue):

    复制代码
    <script setup>
    // 结构出值和修饰符对象
    const [value, modifiers] = defineModel(['trim', 'uppercase'])
    
    // 你可以根据 modifiers 对象来决定如何处理值
    // 例如:如果用了 .trim 修饰符,自动去除空格
    </script>
    
    <template>
      <!-- 这里只是一个示例,通常你不需要手动处理,v-model 会自动处理 .trim 和 .number -->
      <input v-model="value" />
    </template>
  • 父组件:

    复制代码
    <!-- .trim 和 .number 是 Vue 内置的修饰符,会自动生效 -->
    <!-- uppercase 是上面代码中自定义的修饰符名称 -->
    <InputWithModifiers v-model.trim.uppercase="inputText" />

总结

使用 defineModel 的核心优势在于减少样板代码 。过去你需要写 propsemit,现在只需要一行 const model = defineModel(),它本质上是一个语法糖,内部自动帮你生成了响应式引用和更新逻辑。

相关推荐
阿蒙Amon7 小时前
TypeScript学习-第7章:泛型(Generic)
javascript·学习·typescript
睡美人的小仙女1277 小时前
Threejs加载环境贴图报错Bad File Format: bad initial token
开发语言·javascript·redis
fanruitian7 小时前
uniapp android开发 测试板本与发行版本
前端·javascript·uni-app
rayufo7 小时前
【工具】列出指定文件夹下所有的目录和文件
开发语言·前端·python
RANCE_atttackkk7 小时前
[Java]实现使用邮箱找回密码的功能
java·开发语言·前端·spring boot·intellij-idea·idea
摘星编程8 小时前
React Native + OpenHarmony:Timeline垂直时间轴
javascript·react native·react.js
2501_944525549 小时前
Flutter for OpenHarmony 个人理财管理App实战 - 支出分析页面
android·开发语言·前端·javascript·flutter
jin1233229 小时前
React Native鸿蒙跨平台完成剧本杀组队详情页面,可以复用桌游、团建、赛事等各类组队详情页开发
javascript·react native·react.js·ecmascript·harmonyos
李白你好9 小时前
Burp Suite插件用于自动检测Web应用程序中的未授权访问漏洞
前端