什么!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(),它本质上是一个语法糖,内部自动帮你生成了响应式引用和更新逻辑。

相关推荐
五点六六六1 天前
基于 AST 与 Proxy沙箱 的局部代码热验证
前端·设计模式·架构
发现一只大呆瓜1 天前
SSO单点登录:从同域到跨域实战
前端·javascript·面试
发现一只大呆瓜1 天前
告别登录中断:前端双 Token无感刷新
前端·javascript·面试
Cg136269159741 天前
JS-对象-Dom案例
开发语言·前端·javascript
无限大61 天前
《AI观,观AI》:善用AI赋能|让AI成为你深耕核心、推进重心的“最强助手”
前端·后端
烛阴1 天前
Claude Code Skill 从入门到自定义完整教程(Windows 版)
前端·ai编程·claude
lxh01131 天前
数据流的中位数
开发语言·前端·javascript
神仙别闹1 天前
基于NodeJS+Vue+MySQL实现一个在线编程笔试平台
前端·vue.js·mysql
zadyd1 天前
Workflow or ReAct ?
前端·react.js·前端框架
北寻北爱1 天前
vue2和vue3使用less和scss
前端·less·scss