【vue3】v-model 的 “新玩法“

前言

Vue3.6 已经进入 Alpha 阶段,Vue3.4 早在 2023 年 12 月就把 **defineModel**转正式了!

defineModel 是 Vue 3.3+ 中引入的一个编译器宏 (Compile-time Macro),用于简化组件中 v-model的双向绑定实现。它解决了传统 v-model 实现中需要手动声明 props 和 emit 的繁琐问题,让代码更简洁。

defineModel宏简化了这一过程。它会返回一个ref,这个ref可以像普通的数据ref一样使用,但会自动处理prop和事件。

自动实现 v-model 的双向绑定

声明一个响应式的 ref;自动注册对应的 prop(如 modelValue);自动注册对应的事件(如 update:modelValue

defineModel 是什么

一句话定义:让子组件像原生 <input> 一样直接支持 v-model 的语法糖; 说白了就是一个 宏(macro) ,在编译期把 defineModel() 展开成 props + emit

特性:

  1. 自动连接prop和事件:内部自动声明了`modelValue` prop和`update:modelValue`事件。

  2. 支持修饰符:可以处理`v-model`的修饰符(如`.trim`, `.number`)。

如果父组件使用修饰符,例如`v-model.trim`,那么修饰符会以对象的形式传递给`defineModel`的第二个参数。我们可以根据修饰符对值进行处理。

  1. 支持多个`v-model`:通过指定参数,如`defineModel('foo')`,可以用于多个`v-model`绑定(例如`v-model:foo`)。

宏 VS 函数

  • :编译期代码生成,运行时 0 额外开销。

  • 函数 :运行时真实调用。

    因此 defineModel 不需要 import,也不能在普通 <script>.js/.ts 文件里使用。

    复制代码
    // 书写
    const model = defineModel<string>({ default: 'hello' })
    
    // 编译后
    const props = defineProps({
      modelValue: { type: String, default: 'hello' }
    })
    const emit  = defineEmits(['update:modelValue'])
    const model = computed({
      get: () => props.modelValue,
      set: val => emit('update:modelValue', val)
    })

    注意:基于 <script setup>,可直接复制到 *.vue 文件运行。

传统实现 vs defineModel

方式 传统实现 defineModel
代码量 需要手动声明 props + emit + 事件处理 一行声明
数据更新 需调用 emit('update:modelValue', value) 直接给 ref 赋值
修饰符 需通过 props.modelModifiers 手动处理 在 setter 中自动处理
类型安全 需额外类型声明 支持泛型类型

单 v-model

父组件

复制代码
<template>
  <UserName v-model="name" />
  <p>父组件拿到的值:{{ name }}</p>
</template>

<script setup lang="ts">
import { ref } from 'vue'
import UserName from './Names.vue'

const name = ref('名称')
</script>

子组件 Names.vue

复制代码
<template>
  <input v-model="modelValue" />
</template>

<script setup lang="ts">
const modelValue = defineModel<string>()
// 等价于 const modelValue = defineModel<string>({ required: true })
</script>

多个 v-model

父组件

复制代码
<template>
  <UserForm
    v-model:name="form.name"
    v-model:age="form.age"
    v-model:phone="form.phone"
  />
  <pre>{{ form }}</pre>
</template>

<script setup lang="ts">
import { reactive } from 'vue'
import UserForm from './UserForm.vue'

const form = reactive({
  name: '张三',
  age: 18,
  phone: '13800138000'
})
</script>

子组件

复制代码
<template>
  <input v-model="name" placeholder="姓名" />
  <input v-model="age"   placeholder="年龄" />
  <input v-model="phone" placeholder="手机号" />
</template>

<script setup lang="ts">
const name  = defineModel<string>('name')
const age   = defineModel<number>('age')
const phone = defineModel<string>('phone')
</script>

带修饰符 & 转换器

不用手动 .trim

父组件

复制代码
<template>
  <TrimInput v-model.trim="keyword" />
  <p>父组件值:{{ keyword }}</p>
</template>

<script setup lang="ts">
import { ref } from 'vue'
import TrimInput from './TrimInput.vue'

const keyword = ref('')
</script>

子组件TrimInput.vue

复制代码
<template>
  <input v-model="modelValue" />
</template>

<script setup lang="ts">
const [modelValue, modifiers] = defineModel<string, 'trim'>()

// 当父组件写 v-model.trim 时,modifiers.trim === true
if (modifiers.trim) {
  // 通过 set 函数实时转换
}
</script>

实时转换 ,用 get / set

复制代码
const [modelValue, modifiers] = defineModel<string, 'trim'>({
  set(val) {
    return modifiers.trim ? val.trim() : val
  }
})

TypeScript 高阶

需求 写法
必填 defineModel<string>({ required: true })
可选+默认值 defineModel<string>({ default: '张三' })
联合类型 `defineModel<'male'
复杂对象 defineModel<User>()

注意:默认值如果是对象/数组,请用函数返回新实例,避免引用共享:

复制代码
defineModel<string[]>({ default: () => ['A', 'B'] })

注意事项

版本要求:Vue ≥ 3.3,且需配置构建工具:

复制代码
// vite.config.js
export default {
  plugins: [
    vue({
      script: {
        defineModel: true // 启用宏
      }
    })
  ]
}

本质是语法糖:编译后会展开为:

复制代码
// defineModel() 编译结果
const modelValue = defineModel()
// ↓ 编译后 ↓
const modelValue = ref(props.modelValue)
watch(modelValue, (v) => emit('update:modelValue', v))

修饰符处理 :当父组件使用 v-model.trim 时,子组件可通过 defineModelset 选项处理。

总结

defineModel 是 Vue 组件双向绑定的趋势,它:

✅ 减少样板代码(无需手动处理 props/emit)

✅ 提供更直观的响应式体验

✅ 完美支持 TypeScript

✅ 简化修饰符处理

相关推荐
老华带你飞16 分钟前
小区服务|基于Java+vue的小区服务管理系统(源码+数据库+文档)
java·数据库·vue.js·spring boot·论文·毕设·小区服务管理系统
Nan_Shu_61424 分钟前
学习:uniapp全栈微信小程序vue3后台(28)
前端·学习·微信小程序·小程序·uni-app
珍宝商店34 分钟前
原生 JavaScript 方法实战指南
开发语言·前端·javascript
计算机学姐42 分钟前
基于微信小程序的扶贫助农系统【2026最新】
java·vue.js·spring boot·mysql·微信小程序·小程序·mybatis
蓝莓味的口香糖44 分钟前
【企业微信】VUE项目在企微中自定义转发内容
前端·vue.js·企业微信
IT_陈寒44 分钟前
告别低效!用这5个Python技巧让你的数据处理速度提升300% 🚀
前端·人工智能·后端
—Qeyser1 小时前
Laravel + UniApp AES加密/解密
前端·uni-app·laravel
C++chaofan1 小时前
游标查询在对话历史场景下的独特优势
java·前端·javascript·数据库·spring boot
cg.family1 小时前
Vue3 v-slot 详解与示例
前端·javascript·vue.js
FreeBuf_1 小时前
新型域名前置攻击利用Google Meet、YouTube、Chrome及GCP构建流量隧道
前端·chrome