【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

✅ 简化修饰符处理

相关推荐
yangzheui30 分钟前
【VUE2转VUE3学习笔记】-Day1:模板语法
vue.js·笔记·学习
晚霞的不甘41 分钟前
Flutter for OpenHarmony构建全功能视差侧滑菜单系统:从动效设计到多页面导航的完整实践
前端·学习·flutter·microsoft·前端框架·交互
黎子越41 分钟前
python相关练习
java·前端·python
摘星编程1 小时前
用React Native开发OpenHarmony应用:StickyHeader粘性标题
javascript·react native·react.js
A_nanda1 小时前
c# 用VUE+elmentPlus生成简单管理系统
javascript·vue.js·c#
天天进步20151 小时前
Motia事件驱动的内核:深入适配器(Adapter)层看消息队列的流转
javascript
北极糊的狐1 小时前
若依项目vue前端启动键入npm run dev 报错:不是内部或外部命令,也不是可运行的程序或批处理文件。
前端·javascript·vue.js
XRJ040618xrj1 小时前
Nginx下构建PC站点
服务器·前端·nginx
We་ct1 小时前
LeetCode 289. 生命游戏:题解+优化,从基础到原地最优
前端·算法·leetcode·矩阵·typescript
jiayong232 小时前
Vue2 与 Vue3 核心原理对比 - 面试宝典
vue.js·面试·职场和发展