Vue 3 表单完全指南
本文档从零开始讲解 Vue 3 的表单:如何用 v-model 绑定各类输入框、单选、多选、下拉框,如何提交表单、做简单校验,以及 .lazy / .number / .trim 等修饰符,配有大量示例,适合新手系统学习。
一、为什么 Vue 里要专门学“表单”?
1.1 表单里有什么?
页面上和用户“输入、选择”相关的,几乎都是表单相关元素:
- 文本框:
<input type="text">、<textarea> - 单选:
<input type="radio"> - 多选:
<input type="checkbox"> - 下拉:
<select>、<option>
在 Vue 里,我们不会手写 document.querySelector 去拿这些值,而是用 数据 和 v-model 做双向绑定:用户改输入,数据变;我们改数据,输入框显示也跟着变。
所以“Vue 表单”的核心就是:每种控件怎么用 v-model、绑定的值是什么类型、怎么一起提交和校验。
二、文本框(input text / textarea)
2.1 单行文本:input type=”text”
绑定一个 字符串 即可。
用户输入什么,变量就是什么;给变量赋值,输入框就显示什么。
<template>
<div>
<input v-model="username" type="text" placeholder="请输入用户名" />
<p>你输入了:{{ username }}</p>
</div>
</template>
<script setup>
import { ref } from 'vue'
const username = ref('')
</script>
注意: 不要同时写 v-model 和 :value,v-model 已经包含了“绑定 value + 监听 input”的含义,再写 :value 会冲突。
2.2 多行文本:textarea
用法和单行一样,v-model 绑一个字符串。
在 Vue 里,textarea 的内容用 v-model 控制,不要在 <textarea> 中间写静态内容。
<template>
<div>
<textarea v-model="intro" placeholder="个人简介" rows="4"></textarea>
<p>预览:{{ intro }}</p>
</div>
</template>
<script setup>
import { ref } from 'vue'
const intro = ref('')
</script>
2.3 密码框、数字框
type=”password”、type=”number” 等,同样是 v-model 绑字符串(或数字,见下文 .number):
<input v-model="password" type="password" placeholder="密码" />
<input v-model="age" type="number" min="0" max="120" />
若希望 age 在 script 里是数字类型,可加 v-model.number(见“修饰符”一节)。
三、单选与多选
3.1 单个复选框(绑定布尔值)
一个 checkbox,勾选为 true,不勾选为 false:
<template>
<label>
<input type="checkbox" v-model="agree" />
我同意协议
</label>
<p>同意状态:{{ agree }}</p>
</template>
<script setup>
import { ref } from 'vue'
const agree = ref(false)
</script>
3.2 多个复选框(绑定数组)
多个 checkbox 对应“多选”,每个选项有一个 value,v-model 绑一个数组,选中的项的 value 会出现在数组里。
<template>
<div>
<label><input type="checkbox" v-model="hobbies" value="读书" /> 读书</label>
<label><input type="checkbox" v-model="hobbies" value="运动" /> 运动</label>
<label><input type="checkbox" v-model="hobbies" value="音乐" /> 音乐</label>
<p>选中的爱好:{{ hobbies }}</p>
</div>
</template>
<script setup>
import { ref } from 'vue'
const hobbies = ref([])
</script>
- hobbies 必须是数组(如
ref([]))。 - 每个 checkbox 都要有 value,选中的 value 会加入数组,取消则从数组移除。
3.3 单选框(绑定单个值)
多个 radio 为一组,同一个 v-model 绑一个字符串(或数字),选中的那个的 value 就是变量的值。
<template>
<div>
<label><input type="radio" v-model="gender" value="男" /> 男</label>
<label><input type="radio" v-model="gender" value="女" /> 女</label>
<p>性别:{{ gender }}</p>
</div>
</template>
<script setup>
import { ref } from 'vue'
const gender = ref('男')
</script>
- 同一组的 radio 必须绑同一个 v-model 变量。
- 每个 value 决定“选中时赋给变量的值”,可以是字符串或数字。
四、下拉选择(select)
4.1 单选下拉
v-model 绑一个字符串(或数字),值为当前选中的 的 value。
若没有选任何一项,通常给一个空字符串的 option(如“请选择”)。
<template>
<div>
<select v-model="selectedCity">
<option value="">请选择城市</option>
<option value="北京">北京</option>
<option value="上海">上海</option>
<option value="广州">广州</option>
</select>
<p>选中:{{ selectedCity }}</p>
</div>
</template>
<script setup>
import { ref } from 'vue'
const selectedCity = ref('')
</script>
4.2 多选下拉(multiple)
给 select 加 multiple,v-model 绑数组,选中的多个 option 的 value 会组成数组。
<template>
<select v-model="selectedIds" multiple>
<option value="1">选项一</option>
<option value="2">选项二</option>
<option value="3">选项三</option>
</select>
<p>选中:{{ selectedIds }}</p>
</template>
<script setup>
import { ref } from 'vue'
const selectedIds = ref([])
</script>
4.3 选项来自数据(v-for)
实际项目中,选项往往来自接口或列表,用 v-for 生成 option 即可;value 用 :value 绑定数据:
<template>
<select v-model="selectedId">
<option value="">请选择</option>
<option v-for="item in options" :key="item.id" :value="item.id">
{{ item.name }}
</option>
</select>
</template>
<script setup>
import { ref } from 'vue'
const selectedId = ref('')
const options = ref([
{ id: 1, name: '苹果' },
{ id: 2, name: '香蕉' },
{ id: 3, name: '橙子' }
])
</script>
五、v-model 修饰符(.lazy / .number / .trim)
5.1 .lazy —— 失焦或回车后再同步
默认 v-model 是“每输入一个字符就同步一次”。
加上 .lazy 后,改为在 change 时再同步(如失焦、或下拉选完),减少更新次数,适合“不必实时、只要最终值”的场景。
<template>
<input v-model.lazy="message" placeholder="失焦后才会同步" />
<p>当前值:{{ message }}</p>
</template>
<script setup>
import { ref } from 'vue'
const message = ref('')
</script>
5.2 .number —— 转为数字
希望输入框的值在 script 里是数字类型时,可加 .number。
Vue 会尝试把输入转成数字(转不成则仍是字符串)。
<template>
<input v-model.number="age" type="number" />
<p>年龄类型:{{ typeof age }}</p>
</template>
<script setup>
import { ref } from 'vue'
const age = ref(0)
</script>
5.3 .trim —— 去掉首尾空格
自动去掉用户输入的首尾空格,中间空格保留。
<template>
<input v-model.trim="keyword" placeholder="前后空格会被去掉" />
</template>
<script setup>
import { ref } from 'vue'
const keyword = ref('')
</script>
5.4 修饰符可组合
<input v-model.lazy.trim="name" />
<input v-model.number.trim="count" type="number" />
六、表单提交与数据汇总
6.1 用 @submit.prevent 阻止默认提交
表单的 submit 会触发表单默认行为(刷新整页、跳转等)。
在 Vue 里一般用 @submit.prevent 阻止默认,再在方法里用已绑定的数据发请求或做校验。
<template>
<form @submit.prevent="onSubmit">
<input v-model="form.username" placeholder="用户名" />
<input v-model="form.password" type="password" placeholder="密码" />
<button type="submit">登录</button>
</form>
</template>
<script setup>
import { ref } from 'vue'
const form = ref({
username: '',
password: ''
})
function onSubmit() {
console.log('提交数据:', form.value)
// 这里发请求:axios.post('/api/login', form.value)
}
</script>
6.2 用一个对象统一管理表单字段
把表单里所有要提交的字段放在一个 ref 或 reactive 里,提交时直接取这个对象,方便维护和校验:
<template>
<form @submit.prevent="onSubmit">
<input v-model="form.name" placeholder="姓名" />
<input v-model="form.age" type="number" placeholder="年龄" />
<select v-model="form.city">
<option value="">请选择</option>
<option value="北京">北京</option>
<option value="上海">上海</option>
</select>
<label>
<input type="checkbox" v-model="form.agree" /> 同意协议
</label>
<button type="submit">提交</button>
</form>
</template>
<script setup>
import { reactive } from 'vue'
const form = reactive({
name: '',
age: null,
city: '',
agree: false
})
function onSubmit() {
console.log('表单数据:', { ...form })
}
</script>
七、简单校验示例
校验可以在 提交时 做:根据 form 里的值判断,不通过就提示并 return,不继续提交。
<template>
<form @submit.prevent="onSubmit">
<input v-model="form.name" placeholder="姓名" />
<p v-if="errors.name" class="error">{{ errors.name }}</p>
<input v-model.number="form.age" type="number" placeholder="年龄" />
<p v-if="errors.age" class="error">{{ errors.age }}</p>
<button type="submit">提交</button>
</form>
</template>
<script setup>
import { reactive, ref } from 'vue'
const form = reactive({
name: '',
age: null
})
const errors = ref({})
function onSubmit() {
errors.value = {}
if (!form.name.trim()) {
errors.value.name = '请输入姓名'
}
if (form.age == null || form.age === '' || form.age < 0 || form.age > 150) {
errors.value.age = '请输入有效年龄(0-150)'
}
if (Object.keys(errors.value).length > 0) return
console.log('校验通过,提交:', form)
}
</script>
<style scoped>
.error { color: red; font-size: 12px; }
</style>
八、常见场景示例
8.1 登录表单(用户名 + 密码 + 记住我)
<template>
<form @submit.prevent="login">
<input v-model.trim="username" placeholder="用户名" />
<input v-model="password" type="password" placeholder="密码" />
<label>
<input type="checkbox" v-model="remember" /> 记住我
</label>
<button type="submit">登录</button>
</form>
</template>
<script setup>
import { ref } from 'vue'
const username = ref('')
const password = ref('')
const remember = ref(false)
function login() {
console.log({ username: username.value, password: password.value, remember: remember.value })
}
</script>
8.2 注册表单(多字段 + 重复密码校验)
<template>
<form @submit.prevent="register">
<input v-model.trim="form.username" placeholder="用户名" />
<input v-model="form.password" type="password" placeholder="密码" />
<input v-model="form.password2" type="password" placeholder="确认密码" />
<p v-if="form.password && form.password !== form.password2" class="error">两次密码不一致</p>
<input v-model.trim="form.email" type="email" placeholder="邮箱" />
<button type="submit">注册</button>
</form>
</template>
<script setup>
import { reactive } from 'vue'
const form = reactive({
username: '',
password: '',
password2: '',
email: ''
})
function register() {
if (form.password !== form.password2) return alert('两次密码不一致')
console.log('注册数据:', { ...form })
}
</script>
<style scoped>
.error { color: red; }
</style>
8.3 搜索框(回车搜索 + .trim)
<template>
<input
v-model.trim="keyword"
type="text"
placeholder="输入关键词按回车搜索"
@keyup.enter="search"
/>
<button @click="search">搜索</button>
</template>
<script setup>
import { ref } from 'vue'
const keyword = ref('')
function search() {
if (!keyword.value) return
console.log('搜索:', keyword.value)
}
</script>
8.4 筛选表单(多条件,提交时一起用)
<template>
<form @submit.prevent="onFilter">
<input v-model="filter.keyword" placeholder="关键词" />
<select v-model="filter.category">
<option value="">全部分类</option>
<option value="A">分类A</option>
<option value="B">分类B</option>
</select>
<label><input type="checkbox" v-model="filter.onlyInStock" /> 仅显示有货</label>
<button type="submit">筛选</button>
</form>
</template>
<script setup>
import { reactive } from 'vue'
const filter = reactive({
keyword: '',
category: '',
onlyInStock: false
})
function onFilter() {
console.log('筛选条件:', { ...filter })
}
</script>
九、易错点与注意点
9.1 多选(checkbox 组)必须绑数组
多个 checkbox 用同一个 v-model 时,变量必须是 数组(如 ref([])),否则无法正确收集多个选中项。
9.2 不要同时写 v-model 和 :value / @input
v-model 本质是 :value + @input(或对应控件的 value/change)。
同一元素上不要再写 :value 或 @input,否则会覆盖或冲突。
9.3 select 的“请选择”用 value=””
若希望初始是“未选”,给一个 ,且 v-model 初始值设为 ”;若选项来自接口,要等数据到了再绑,否则可能选不中。
9.4 提交前可再拷贝一份数据
提交时若直接传 form 对象,若后面还要清空表单,注意引用关系;必要时 JSON.parse(JSON.stringify(form)) 或 { …form } 提交副本,避免把响应式对象直接发给后端带来意外。
十、表单控件与 v-model 值类型速查表
| 控件 | v-model 绑定的值类型 | 说明 |
|---|---|---|
| input text / textarea | 字符串 | 可加 .trim、.lazy |
| input number | 字符串或数字 | 建议 .number 得到数字 |
| 单个 checkbox | 布尔值 | 勾选 true,不勾选 false |
| 多个 checkbox | 数组 | 选中项的 value 组成数组 |
| radio | 字符串(或数字) | 选中项的 value |
| select 单选 | 字符串(或数字) | 选中 option 的 value |
| select 多选 | 数组 | 选中 option 的 value 数组 |
十一、学习建议
- 先练熟每种控件:text / textarea → 单 checkbox → 多 checkbox → radio → select,各自绑什么类型(字符串、布尔、数组)。
- 表单数据用一个对象(ref/reactive)统一管理,提交时用 @submit.prevent 在方法里取这个对象。
- 校验在 onSubmit 里做,用单独一个 errors 对象存错误信息,模板里用 v-if 显示。
- 善用 .lazy、.number、.trim,按需组合。
把本文档里的示例在项目里敲一遍、改一改,会掌握得更牢。事件修饰符见《Vue3事件处理.md》,v-model 原理见《Vue3指令.md》。祝你学习顺利。