Learn
Vue/10-forms-and-v-model

表单与 v-model

v-model 是 Vue 对「表单输入 ↔ 数据」双向绑定的语法糖。它背后就是 :value + @input 的组合。

1. 基本用法

<script setup lang="ts">
import { ref } from 'vue';
const text = ref('');
const checked = ref(false);
const picked = ref('A');
const selected = ref('');
</script>
 
<template>
  <input v-model="text" placeholder="输入文字" />
  <textarea v-model="text"></textarea>
 
  <input type="checkbox" v-model="checked" /> {{ checked }}
 
  <input type="radio" value="A" v-model="picked" /> A
  <input type="radio" value="B" v-model="picked" /> B
 
  <select v-model="selected">
    <option disabled value="">请选择</option>
    <option>A</option>
    <option>B</option>
  </select>
</template>

不同元素 v-model 监听的事件不同:文本类用 input,checkbox/radio/select 用 change。这是语法糖自动处理的,你不用管。

2. 修饰符

<input v-model.lazy="text" />   <!-- 失焦/change 时才更新,而非每次输入 -->
<input v-model.number="age" />  <!-- 自动 parseFloat,转成数字 -->
<input v-model.trim="name" />   <!-- 自动去首尾空格 -->

可以串联:v-model.lazy.trim="x"。

3. 在组件上用 v-model

父组件里 v-model="x" 等价于给子组件传 :model-value="x" 并监听 @update:model-value。

Vue 3.4+ 推荐写法:子组件用 defineModel

<!-- ChildInput.vue -->
<script setup lang="ts">
const model = defineModel<string>(); // 自动拿到 prop + 提供 emit
</script>
 
<template>
  <input :value="model" @input="model = ($event.target as HTMLInputElement).value" />
</template>
<!-- 父 -->
<ChildInput v-model="name" />

defineModel() 返回的就是一个 ref:读它拿到当前值,写它(model = xxx)就自动向上 emit('update:modelValue', xxx)。一个组件还能有多个 v-model:

<UserName v-model:first="first" v-model:last="last" />
const first = defineModel<string>('first');
const last = defineModel<string>('last');

Vue 3.3 及更早的写法(没有 defineModel 时):手动声明 prop + emit。

const props = defineProps<{ modelValue: string }>();
const emit = defineEmits<{ 'update:modelValue': [value: string] }>();
// 模板里: :value="props.modelValue" @input="emit('update:modelValue', ...)"

4. 多个复选框绑定到数组

<script setup lang="ts">
import { ref } from 'vue';
const fruits = ref<string[]>([]);
</script>
 
<template>
  <input type="checkbox" value="apple" v-model="fruits" /> 苹果
  <input type="checkbox" value="banana" v-model="fruits" /> 香蕉
  <!-- fruits 会是 ['apple','banana',...] -->
</template>

5. 自定义修饰符

组件上的 v-model 还能带自定义修饰符,通过 defineModel 的 .modifiers 拿到:

<script setup lang="ts">
const [model, modifiers] = defineModel<string>({
  set(value) {
    if (modifiers.capitalize) return value.charAt(0).toUpperCase() + value.slice(1);
    return value;
  },
});
</script>
<MyInput v-model.capitalize="name" />

小结

  • v-model = :value + @input(文本)/ change(checkbox/radio/select)的糖
  • 修饰符:.lazy(change 更新)、.number(转数字)、.trim(去空格)
  • 组件上 v-model 通过 defineModel()(3.4+)实现,多模型用 v-model:xxx
  • 多个同名 checkbox 绑定到同一数组;自定义修饰符走 defineModel 的 set 拦截
🎯练习 1:受控输入框

写一个绑定到 ref 的文本框,要求「输入时实时显示长度」,并在长度超过 10 时把边框变红(用 :class 即可)。

🎯练习 2:用 defineModel 写数字输入框

写一个 <NumberInput> 组件,父组件用 <NumberInput v-model="age" /> 拿到数字(不是字符串)。用 defineModel + .number 思路实现(在组件内把输入转成 number 再 emit 出去)。

🎯练习 3:多 v-model 组件

写一个 <FullName> 组件,父组件可以同时 v-model:first 和 v-model:last 双向绑定姓和名,并在组件内显示「全名 = 姓+名」。