下面给你整理一份 Vue 3 中 v-model 双向数据绑定的全面指南,覆盖基础用法、命名 v-model、组件自定义绑定、修饰符以及进阶技巧,附带完整示例。


1️⃣ 基础用法

在 Vue 3 中,v-model 用于实现父组件和子组件之间的 双向绑定

<template>
  <div>
    <input v-model="message" placeholder="请输入内容" />
    <p>你输入的内容:{{ message }}</p>
  </div>
</template>

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

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

✅ 解释:

  • v-model="message" 等价于: <input :value="message" @input="message = $event" />
  • ref 创建响应式数据

2️⃣ 组件内使用 v-model

2.1 父组件

&lt;MyInput v-model="username" />
&lt;p>用户名:{{ username }}&lt;/p>

2.2 子组件实现

&lt;template>
  &lt;input :value="modelValue" @input="$emit('update:modelValue', $event)" />
&lt;/template>

&lt;script setup lang="ts">
import { defineProps } from 'vue'

const props = defineProps&lt;{ modelValue: string }>()
&lt;/script>

✅ 说明:

  • Vue 3 默认使用 modelValue + update:modelValue 作为双向绑定的约定
  • 子组件通过 $emit('update:modelValue', value) 更新父组件

3️⃣ 多个 v-model(命名 v-model)

Vue 3 支持 一个组件多个 v-model

父组件:

&lt;MyInput v-model:title="title" v-model:content="content" />

子组件:

&lt;template>
  &lt;input
    :value="title"
    @input="$emit('update:title', $event)"
    placeholder="标题"
  />
  &lt;textarea
    :value="content"
    @input="$emit('update:content', $event)"
    placeholder="内容"
  >&lt;/textarea>
&lt;/template>

&lt;script setup lang="ts">
import { defineProps } from 'vue'

const props = defineProps&lt;{
  title: string
  content: string
}>()
&lt;/script>

✅ 说明:

  • v-model:xxx 对应子组件的 props$emit('update:xxx')
  • 可以实现一个组件多个双向绑定

4️⃣ v-model 修饰符

修饰符用途
.lazy绑定值在 change 事件触发时更新
.number自动将输入值转为数字
.trim自动去掉首尾空格

示例:

&lt;input v-model.number="age" placeholder="请输入年龄" />
&lt;input v-model.trim="username" placeholder="请输入用户名" />
&lt;input v-model.lazy="email" placeholder="请输入邮箱" />


5️⃣ 自定义组件内实现修饰符

如果子组件也想支持修饰符,需要在 $emit 时处理:

&lt;script setup lang="ts">
import { defineProps, toRef } from 'vue'

const props = defineProps&lt;{ modelValue: string }>()
const value = toRef(props, 'modelValue')

// 自定义处理 trim
const onInput = (e: Event) => {
  const input = e.target as HTMLInputElement
  const val = input.value.trim()
  value.value = val
  emit('update:modelValue', val)
}
&lt;/script>


6️⃣ 结合 Composition API 使用 v-model

&lt;script setup lang="ts">
import { ref, defineProps, defineEmits } from 'vue'

const props = defineProps&lt;{ modelValue: string }>()
const emit = defineEmits(['update:modelValue'])

const localValue = ref(props.modelValue)

const updateValue = (val: string) => {
  localValue.value = val
  emit('update:modelValue', val)
}
&lt;/script>

&lt;template>
  &lt;input :value="localValue" @input="updateValue($event.target.value)" />
&lt;/template>

✅ 说明:

  • localValue 保持子组件内部响应式
  • 使用 $emit 通知父组件更新

7️⃣ v-model 与 TypeScript 类型安全

const props = defineProps&lt;{ modelValue: string | number }>()
const emit = defineEmits&lt;(e: 'update:modelValue', value: string | number) => void>()

✅ 优势:

  • 编辑器自动提示类型
  • 提高组件可维护性

8️⃣ 进阶示例:表单组件

&lt;template>
  &lt;form @submit.prevent="submitForm">
    &lt;input v-model.trim="form.username" placeholder="用户名" />
    &lt;input v-model.number="form.age" placeholder="年龄" type="number" />
    &lt;button type="submit">提交&lt;/button>
  &lt;/form>
&lt;/template>

&lt;script setup lang="ts">
import { reactive } from 'vue'

const form = reactive({
  username: '',
  age: 0
})

const submitForm = () => {
  console.log('提交表单:', form)
}
&lt;/script>


9️⃣ 总结

  1. 基础用法v-model="state"
  2. 组件内双向绑定props.modelValue + $emit('update:modelValue', val)
  3. 命名 v-modelv-model:xxx 对应 props.xxx + update:xxx
  4. 修饰符.lazy.number.trim
  5. TypeScript 类型defineProps + defineEmits 保证类型安全
  6. Composition API:可在子组件内部维护本地响应式 ref,通过 $emit 同步父组件