表单与 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 双向绑定姓和名,并在组件内显示「全名 = 姓+名」。