Vue 表单与 v-model

表单是前端最繁琐的部分:文本框、复选框、单选、下拉、文件上传……每种控件的取值方式都不一样。Vue 用一个 v-model 指令把所有表单控件统一抽象成"双向绑定",极大简化了表单开发。本章覆盖所有常见表单场景。

1. v-model 的本质

先理解 v-model 到底在做什么。它本质是两个绑定的语法糖:

对每种控件(input/checkbox/radio/select),Vue 内部会自动用对应的属性和事件,你只需要写 v-model,剩下的交给 Vue。

2. 文本框与 textarea

最基础的场景:<input><textarea> 都直接用 v-model

<template>
  <!-- 文本框:输入即同步 -->
  <input v-model="username" placeholder="用户名" />
  <p>你好,{{ username }}</p>

  <!-- 多行文本:同样用 v-model,注意不要写成 {{ }} -->
  <textarea v-model="content" placeholder="说点什么"></textarea>
  <p>你输入了:{{ content }}</p>

  <!-- 密码框 -->
  <input type="password" v-model="password" />
</template>

<script setup>
import { ref } from "vue";
const username = ref("");
const content = ref("");
const password = ref("");
</script>

注意:textarea 里不要写插值 {{ content }}——它只显示初始值,后续不会更新。永远用 v-model

3. 复选框(checkbox)

复选框分两种用法:单个绑布尔值、多个绑数组。

<template>
  <!-- 单个复选框:绑定布尔值 -->
  <input type="checkbox" v-model="agree" /> 同意条款
  <p>{{ agree ? "已同意" : "未同意" }}</p>

  <!-- 多个复选框:绑定数组,每个 input 写不同的 value -->
  <p>选你喜欢的:
    <label><input type="checkbox" v-model="hobbies" value="read" /> 阅读</label>
    <label><input type="checkbox" v-model="hobbies" value="music" /> 音乐</label>
    <label><input type="checkbox" v-model="hobbies" value="sport" /> 运动</label>
  </p>
  <p>已选:{{ hobbies.join(", ") }}</p>
</template>

<script setup>
import { ref } from "vue";
const agree = ref(false);
const hobbies = ref([]);   // 注意初始值是数组
</script>

多个复选框的关键点:多个 checkbox 用同一个 v-model,每个 input 写不同的 value,Vue 会自动把选中的 value 收集成数组。初始值必须是数组([])。

4. 单选框(radio)

单选框用法和"多复选框"几乎一样——同一个 v-model,不同的 value。Vue 自动让它们互斥。

<template>
  <!-- 单选框:绑定值,每个 input 写不同的 value -->
  <p>性别:
    <label><input type="radio" v-model="gender" value="M" /> 男</label>
    <label><input type="radio" v-model="gender" value="F" /> 女</label>
  </p>
  <p>你选了:{{ gender }}</p>

  <!-- 单选按钮组:同一个 v-model 自动互斥 -->
  <p>评分:
    <label v-for="n in 5" :key="n">
      <input type="radio" v-model="rating" :value="n" /> {{ n }}
    </label>
  </p>
</template>

<script setup>
import { ref } from "vue";
const gender = ref("M");
const rating = ref(5);
</script>

用 v-for 渲染单选项是常见模式,记得给 :value:key

5. 下拉选择(select)

select 的 v-model 写在 <select> 上(不是 option),选项通过 value 区分:

<template>
  <!-- 单选下拉:绑定基本类型 -->
  <select v-model="city">
    <option value="">请选择</option>
    <option value="bj">北京</option>
    <option value="sh">上海</option>
    <option value="gz">广州</option>
  </select>
  <p>你选了:{{ city }}</p>

  <!-- 多选下拉:加 multiple,绑定数组 -->
  <select v-model="tags" multiple>
    <option value="vue">Vue</option>
    <option value="react">React</option>
    <option value="svelte">Svelte</option>
  </select>
  <p>标签:{{ tags.join(", ") }}</p>

  <!-- v-for 动态渲染选项 -->
  <select v-model="userId">
    <option v-for="u in users" :key="u.id" :value="u.id">
      {{ u.name }}
    </option>
  </select>
</template>

<script setup>
import { ref } from "vue";
const city = ref("");
const tags = ref([]);
const userId = ref(1);
const users = ref([
  { id: 1, name: "小明" },
  { id: 2, name: "小红" },
]);
</script>

几个注意点:

6. v-model 的三个修饰符

修饰符改变 v-model 的同步行为:

<template>
  <!-- 默认:每次 input 事件都同步(输入即变) -->
  <input v-model="text1" />

  <!-- .lazy:改为 change 事件触发(失焦或回车时才同步) -->
  <input v-model.lazy="text2" />

  <!-- .number:自动转数字(否则是字符串) -->
  <input v-model.number="age" type="number" />
  <p>age 类型:{{ typeof age }}</p>

  <!-- .trim:自动去除首尾空格 -->
  <input v-model.trim="name" />

  <!-- 链式 -->
  <input v-model.lazy.trim="name" />
</template>

<script setup>
import { ref } from "vue";
const text1 = ref("");
const text2 = ref("");
const age = ref(0);
const name = ref("");
</script>

修饰符可以链式:v-model.lazy.trim.number,三个都生效。

7. 表单的常见痛点与解法

8. 自定义组件的 v-model

Vue 3 允许把 v-model 用在你自己写的组件上——这是封装表单控件的核心机制。比如自己写一个带样式的输入框:

<!-- CustomInput.vue -->
<template>
  <!-- 写法一:基于 modelValue prop + update:modelValue 事件 -->
  <input
    :value="modelValue"
    @input="$emit('update:modelValue', $event.target.value)"
  />
</template>

<script setup>
defineProps(["modelValue"]);
defineEmits(["update:modelValue"]);
</script>

<!-- 父组件 -->
<template>
  <!-- v-model 用在自定义组件上 -->
  <CustomInput v-model="msg" />
  <!-- 等价于: -->
  <CustomInput :model-value="msg" @update:model-value="msg = $event" />
</template>

<script setup>
import { ref } from "vue";
import CustomInput from "./CustomInput.vue";
const msg = ref("");
</script>

原理:子组件接收 modelValue 作为 prop,触发 update:modelValue 事件通知父组件。父组件的 v-model 自动展开成这两个绑定。

Vue 3 还支持多个 v-model(给每个加参数):<UserForm v-model:name="name" v-model:age="age" />。子组件分别声明 name 和 age 即可。这让组件的复用性大大提升。

9. 实战:登录表单

把上面所有知识组合起来,写一个最经典的登录表单:

<template>
  <form @submit.prevent="onSubmit">
    <div>
      <label>账号</label>
      <input v-model.trim="form.account" placeholder="手机号" />
    </div>
    <div>
      <label>密码</label>
      <input type="password" v-model="form.password" />
    </div>
    <div>
      <label><input type="checkbox" v-model="form.remember" /> 记住我</label>
    </div>
    <button type="submit">登录</button>
    <p v-if="error" style="color: red">{{ error }}</p>
  </form>
</template>

<script setup>
import { reactive, ref } from "vue";
const form = reactive({
  account: "",
  password: "",
  remember: false,
});
const error = ref("");

function onSubmit() {
  if (!form.account) return (error.value = "请输入账号");
  if (form.password.length < 6) return (error.value = "密码至少 6 位");
  error.value = "";
  alert("登录成功:" + form.account);
}
</script>

注意几个细节:

小结

这一章你掌握了 Vue 表单开发的所有基础:v-model 在不同控件上的用法、三个修饰符、自定义组件的 v-model。表单是前端开发的高频场景,这一章学透,你能少写一半代码。下一篇我们看 Vue 的核心组织单元——组件。

← 上一篇 Vue 事件处理

下一篇 Vue 组件基础

✈️💬