在这里插入图片描述



前言

在 Vue3 开发中,双向绑定是高频需求 —— 从表单输入到自定义组件交互,我们经常需要在父子组件间同步数据。在 Vue3.4 之前,实现双向绑定往往需要手动定义 props 和 emit,代码冗余且容易出错。而 defineModel 的出现,彻底简化了这一流程,让双向绑定从 “繁琐配置” 变成 “一行代码”。本文将从基础到进阶,带你全面掌握 defineModel 的用法与原理。


一、什么是 defineModel?

defineModel 是 Vue3.4+ 推出的 Composition API 专属特性,本质是 Vue 为双向绑定提供的「语法糖」。它的核心作用是:自动帮我们完成 props 和 emit 的配置,无需手动定义 modelValue 和 update:modelValue,直接实现父子组件数据同步。

在defineModel出现之前,实现双向绑定需要三步:声明 prop 接收值、声明 emit 事件更新值、在组件内部维护状态并触发事件。而有了defineModel,这一切都变得简单优雅。

先看痛点:旧方案的繁琐

在 Vue3.4 之前,实现组件双向绑定(v-model)需要两步:

  1. 子组件定义 props: { modelValue: … } 接收父组件数据;
  2. 子组件通过 emit(‘update:modelValue’, 新值) 通知父组件更新。

举个输入框组件的例子,旧方案代码如下:

<!-- 子组件:OldInput.vue -->
<template>
  <input 
    type="text" 
    :value="modelValue" 
    @input="$emit('update:modelValue', $event.target.value)"
  />
</template>

<script setup>
// 手动定义 props 和 emit
const props = defineProps({
  modelValue: {
    type: String,
    required: true
  }
})
const emit = defineEmits(['update:modelValue'])
</script>

父组件使用时:

<!-- 父组件 -->
<template>
  <OldInput v-model="username" />
</template>

<script setup>
import { ref } from 'vue'
const username = ref('')
</script>

看似简单,但当组件逻辑复杂(比如多双向绑定、带默认值)时,props 和 emit 的配置会越来越臃肿,且容易出现「漏写 emit」「props 类型错误」等问题。

defineModel 的优势:一行搞定

而用 defineModel 重构后,子组件代码直接简化为:

<!-- 子组件:NewInput.vue -->
<template>
  <input type="text" v-model="model" />
</template>

<script setup>
// 一行代码替代 props + emit
const model = defineModel()
</script>

父组件用法完全不变,但子组件代码减少了 80%,且无需担心 props 和 emit 的配置错误 —— 这就是 defineModel 的核心价值:简化双向绑定流程,降低开发成本。


二、前置知识:使用 defineModel 的前提

在开始使用前,需要确认两个前提:

  1. Vue 版本 ≥ 3.4:defineModel 是 Vue3.4 正式引入的特性,低于此版本无法使用(可通过 npm update vue 升级);
  2. 必须在 < script setup> 中使用:defineModel 是 Composition API 特性,不支持 Options API(如 < script> 无 setup 语法);
  3. 无需额外导入:defineModel 是 Vue 全局注入的 API,像defineProps、defineEmits一样,直接在 < script setup> 中使用即可。

二、使用方法

1、基础用法

基础用法的核心是「子组件用 defineModel 定义绑定值,父组件用 v-model 传值」,适用于大多数简单场景(如输入框、开关、单选框等)。

<!-- MyInput.vue -->
<template>
  <input v-model="modelValue" />
</template>

<script setup>
import { defineModel } from 'vue'

// 声明可被v-model绑定的变量
const modelValue = defineModel()
</script>

父组件使用时与普通 v-model 完全一致:

<template>
  <div>
    <MyInput v-model="username" />
    <p>输入的用户名: {{ username }}</p>
  </div>
</template>

<script setup>
import { ref } from 'vue'
import MyInput from './MyInput.vue'

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

无需声明 props 和 emits,一个双向绑定的组件就完成了。


2、多模型绑定

defineModel支持多个 v-model 绑定,只需指定不同的名称:

<!-- UserForm.vue -->
<template>
  <div>
    <input v-model="name" placeholder="姓名" />
    <input v-model="age" type="number" placeholder="年龄" />
  </div>
</template>

<script setup>
import { defineModel } from 'vue'

const name = defineModel('name') // 对应v-model:name
const age = defineModel('age')  // 对应v-model:age
</script>

父组件使用多个 v-model:

<template>
  <UserForm 
    v-model:name="userName"
    v-model:age="userAge"
  />
</template>

<script setup>
import { ref } from 'vue'
import UserForm from './UserForm.vue'

const userName = ref('')
const userAge = ref(0)
</script>

3、指定类型与默认值

实际开发中,我们需要明确数据类型(避免类型错误),并设置默认值(避免 undefined)。

defineModel 支持传入一个「配置对象」,格式如下:

const model = defineModel({
  type: String,    // 指定值的类型(可选,默认 any)
  default: '默认值',// 设置默认值(可选)
  event: 'update:myEvent', // 自定义触发事件名(可选,默认 update:modelValue)
  required: false  // 是否必传(可选,默认 false)
})

带类型和默认值的输入框

<!-- 子组件:TypedInput.vue -->
<template>
  <input 
    type="text" 
    v-model="model" 
    placeholder="请输入用户名"
  />
</template>

<script setup>
// 1. 指定类型为 String,默认值为 "游客"
const model = defineModel({
  type: String,
  default: '游客' // 基础类型(String/Number/Boolean)直接写默认值
})

// 验证:初始值是默认值 "游客"
console.log('初始值:', model.value) // 输出 "游客"
</script>

父组件使用(可选不传值,会用默认值):

<!-- 父组件 -->
<template>
  <!-- 不传 v-model,子组件用默认值 "游客" -->
  <TypedInput />
  <!-- 传 v-model,覆盖默认值 -->
  <TypedInput v-model="username" />
</template>

<script setup>
import { ref } from 'vue'
import TypedInput from './TypedInput.vue'
const username = ref('张三')
</script>

4、自定义修饰符

vue 的 v-model 本身支持一些内置修饰符(如 .trim、.number、.lazy),而 defineModel 允许我们自定义修饰符逻辑,让组件更智能地处理不同场景的需求。

认识修饰符语法

defineModel 可以返回一个数组,包含两个元素:

  1. 第一个元素:绑定的值(和之前的 model 用法一致)
  2. 第二个元素:修饰符对象(包含父组件传递的所有修饰符)

语法如下:

// 解构获取值和修饰符
const [modelValue, modifiers] = defineModel()

// modifiers 是一个对象,键为修饰符名称,值为 true(表示使用了该修饰符)
console.log(modifiers) // { trim: true, uppercase: true }(示例)

基础示例:实现 .trim 修饰符

内置的 .trim 修饰符可以自动去除输入首尾空格,但我们可以通过自定义修饰符逻辑理解其原理:

<!-- 子组件:TrimInput.vue -->
<template>
  <input 
    type="text" 
    :value="modelValue" 
    @input="handleInput"
  />
</template>

<script setup>
// 1. 获取值和修饰符
const [modelValue, modifiers] = defineModel({
  type: String
})

// 2. 处理输入事件
const handleInput = (e) => {
  let value = e.target.value
  
  // 3. 如果使用了 .trim 修饰符,则去除首尾空格
  if (modifiers.trim) {
    value = value.trim()
  }
  
  // 4. 更新值
  modelValue.value = value
}
</script>

父组件使用

<template>
  <TrimInput v-model.trim="username" />
</template>

<script setup>
import { ref } from 'vue'
import TrimInput from './TrimInput.vue'
const username = ref('')
</script>

当用户输入 " 张三 " 时,由于使用了 .trim 修饰符,最终会自动处理为 “张三”。

进阶示例:自定义多修饰符组合使用

实际开发中,我们可能需要同时处理多个修饰符。例如,实现一个支持 .uppercase(转为大写)和 .prefix(添加前缀#)的输入框:

<!-- 子组件:CustomInput.vue -->
<template>
  <input v-model="processedValue" />
</template>

<script setup>
import { computed } from "vue";

// 获取自定义修饰符
const [modelValue, modifiers] = defineModel();

// 双向绑定计算属性,并通过get/set处理自定义修饰符
const processedValue = computed({
  get() {
    return modelValue.value;
  },
  set(value) {
    let newValue = value;
    // 如果使用了.uppercase修饰符,则转换为大写
    if (modifiers.uppercase) {
      newValue = value.toUpperCase();
    }
    //如果使用了.prefix修饰符添加 # 前缀
    if (modifiers.prefix) {
      newValue = newValue.indexOf("#") === 0 ? newValue : `#${newValue}`; // 添加 # 前缀
    }
    modelValue.value = newValue;
  },
});
</script>

父组件使用多个修饰符::

<template>
  <CustomInput v-model.prefix.uppercase="message" />
  <p>处理后的值: {{ message }}</p>
</template>

<script setup>
import { ref } from 'vue'
import CustomInput from './CustomInput.vue'

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

运行效果:
请添加图片描述


总结

defineModel为 Vue3 组件开发带来了显著的便利性,尤其是在处理双向绑定和修饰符时。它不仅减少了模板代码,还让组件逻辑更加清晰直观。通过支持内置修饰符和自定义修饰符,defineModel为表单处理等场景提供了强大的灵活性。掌握defineModel的使用,将帮助你在 Vue3 项目中编写更简洁、更易维护的组件代码。随着 Vue 生态的不断发展,这类语法糖会越来越多,及时掌握这些新特性,能让我们的开发效率得到质的提升。

更多推荐