跳到主要内容

Vue 表单绑定

表单是 Web 应用中最常见的交互形式。Vue 提供了 v-model 指令来实现表单元素与数据的双向绑定。本章将详细介绍表单绑定的各种用法。

基础用法

文本输入

<input v-model="message" />
<p>输入的内容: {{ message }}</p>
<script setup>
import { ref } from 'vue'
const message = ref('')
</script>

v-model 会自动同步 input 元素的值和 Vue 的数据变量。

多行文本

<textarea v-model="message"></textarea>
<p>输入的内容: {{ message }}</p>

复选框

单个复选框:

<input type="checkbox" id="checkbox" v-model="checked" />
<label for="checkbox">{{ checked ? '已勾选' : '未勾选' }}</label>
<script setup>
import { ref } from 'vue'
const checked = ref(false)
</script>

多个复选框(绑定到数组):

<div>
<input type="checkbox" id="apple" value="苹果" v-model="fruits" />
<label for="apple">苹果</label>

<input type="checkbox" id="banana" value="香蕉" v-model="fruits" />
<label for="banana">香蕉</label>

<input type="checkbox" id="orange" value="橙子" v-model="fruits" />
<label for="orange">橙子</label>
</div>
<p>选择的水果: {{ fruits }}</p>
<script setup>
import { ref } from 'vue'
const fruits = ref([])
</script>

单选按钮

<div>
<input type="radio" id="male" value="" v-model="gender" />
<label for="male"></label>

<input type="radio" id="female" value="" v-model="gender" />
<label for="female"></label>
</div>
<p>选择的性别: {{ gender }}</p>
<script setup>
import { ref } from 'vue'
const gender = ref('男')
</script>

下拉选择

单选下拉框:

<select v-model="selected">
<option value="">请选择</option>
<option value="a">选项 A</option>
<option value="b">选项 B</option>
<option value="c">选项 C</option>
</select>
<p>选择的值: {{ selected }}</p>
<script setup>
import { ref } from 'vue'
const selected = ref('')
</script>

多选下拉框(绑定到数组):

<select v-model="selectedMultiple" multiple>
<option value="a">选项 A</option>
<option value="b">选项 B</option>
<option value="c">选项 C</option>
</select>
<p>选择的值: {{ selectedMultiple }}</p>
<script setup>
import ref from 'vue'
const selectedMultiple = ref([])
</script>

v-model 修饰符

v-model 提供了三个修饰符来简化常见需求:

.lazy - 延迟同步

默认情况下,v-model 在每次 input 事件后同步数据。使用 .lazy 会改为在 change 事件后才同步(通常是失去焦点时):

<!-- 失去焦点或回车后才同步 -->
<input v-model.lazy="message" />
<script setup>
import { ref } from 'vue'
const message = ref('')
</script>

.number - 自动转换为数字

将输入自动转换为数字类型:

<input v-model.number="age" />
<p>类型: {{ typeof age }}</p>
<script setup>
import { ref } from 'vue'
const age = ref(0)
</script>

如果没有 .number,输入的数字会是字符串类型。

.trim - 去除首尾空格

自动去除输入内容的首尾空白:

<input v-model.trim="username" />
<p>用户名: '{{ username }}'</p>
<script setup>
import { ref } from 'vue'
const username = ref('')
</script>

表单验证

Vue 本身不提供表单验证,但可以结合计算属性或自定义逻辑实现:

必填项验证

<script setup>
import { ref, computed } from 'vue'

const username = ref('')
const email = ref('')

const usernameError = computed(() => {
if (!username.value) return '用户名不能为空'
if (username.value.length < 3) return '用户名至少3个字符'
return ''
})

const emailError = computed(() => {
if (!email.value) return '邮箱不能为空'
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
if (!emailRegex.test(email.value)) return '邮箱格式不正确'
return ''
})

const isValid = computed(() => {
return !usernameError.value && !emailError.value
})

function handleSubmit() {
if (!isValid.value) return
console.log('提交表单')
}
</script>

<template>
<form @submit.prevent="handleSubmit">
<div>
<input v-model="username" placeholder="用户名" />
<span v-if="usernameError" class="error">{{ usernameError }}</span>
</div>

<div>
<input v-model="email" placeholder="邮箱" />
<span v-if="emailError" class="error">{{ emailError }}</span>
</div>

<button type="submit" :disabled="!isValid">提交</button>
</form>
</template>

<style scoped>
.error {
color: red;
font-size: 12px;
}
</style>

示例:完整的表单组件

<script setup>
import { ref, computed } from 'vue'

// 表单数据
const form = ref({
username: '',
email: '',
password: '',
confirmPassword: '',
gender: 'male',
interests: [],
country: '',
bio: ''
})

// 选项数据
const genderOptions = [
{ value: 'male', label: '男' },
{ value: 'female', label: '女' }
]

const interestOptions = ['编程', '音乐', '运动', '阅读']
const countryOptions = [
{ value: 'cn', label: '中国' },
{ value: 'us', label: '美国' },
{ value: 'jp', label: '日本' }
]

// 验证
const errors = computed(() => {
const errs = {}

if (!form.value.username) errs.username = '用户名不能为空'
else if (form.value.username.length < 3) errs.username = '用户名至少3个字符'

if (!form.value.email) errs.email = '邮箱不能为空'
else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(form.value.email))
errs.email = '邮箱格式不正确'

if (!form.value.password) errs.password = '密码不能为空'
else if (form.value.password.length < 6) errs.password = '密码至少6位'

if (form.value.password !== form.value.confirmPassword)
errs.confirmPassword = '两次密码不一致'

if (!form.value.country) errs.country = '请选择国家'

return errs
})

const isValid = computed(() => Object.keys(errors.value).length === 0)

function handleSubmit() {
if (!isValid.value) return
console.log('表单数据:', form.value)
alert('提交成功!')
}

function handleReset() {
form.value = {
username: '',
email: '',
password: '',
confirmPassword: '',
gender: 'male',
interests: [],
country: '',
bio: ''
}
}
</script>

<template>
<form class="form" @submit.prevent="handleSubmit">
<h2>用户注册</h2>

<!-- 用户名 -->
<div class="form-group">
<label>用户名</label>
<input v-model="form.username" placeholder="请输入用户名" />
<span v-if="errors.username" class="error">{{ errors.username }}</span>
</div>

<!-- 邮箱 -->
<div class="form-group">
<label>邮箱</label>
<input v-model="form.email" type="email" placeholder="请输入邮箱" />
<span v-if="errors.email" class="error">{{ errors.email }}</span>
</div>

<!-- 密码 -->
<div class="form-group">
<label>密码</label>
<input v-model="form.password" type="password" placeholder="请输入密码" />
<span v-if="errors.password" class="error">{{ errors.password }}</span>
</div>

<!-- 确认密码 -->
<div class="form-group">
<label>确认密码</label>
<input v-model="form.confirmPassword" type="password" placeholder="请确认密码" />
<span v-if="errors.confirmPassword" class="error">{{ errors.confirmPassword }}</span>
</div>

<!-- 性别 -->
<div class="form-group">
<label>性别</label>
<label v-for="opt in genderOptions" :key="opt.value">
<input
type="radio"
:value="opt.value"
v-model="form.gender"
/>
{{ opt.label }}
</label>
</div>

<!-- 兴趣 -->
<div class="form-group">
<label>兴趣</label>
<label v-for="interest in interestOptions" :key="interest">
<input
type="checkbox"
:value="interest"
v-model="form.interests"
/>
{{ interest }}
</label>
</div>

<!-- 国家 -->
<div class="form-group">
<label>国家</label>
<select v-model="form.country">
<option value="">请选择</option>
<option
v-for="opt in countryOptions"
:key="opt.value"
:value="opt.value"
>
{{ opt.label }}
</option>
</select>
<span v-if="errors.country" class="error">{{ errors.country }}</span>
</div>

<!-- 简介 -->
<div class="form-group">
<label>简介</label>
<textarea v-model="form.bio" rows="4" placeholder="请输入简介"></textarea>
</div>

<!-- 按钮 -->
<div class="buttons">
<button type="submit" :disabled="!isValid">提交</button>
<button type="button" @click="handleReset">重置</button>
</div>

<!-- 预览 -->
<div class="preview">
<h3>表单预览</h3>
<pre>{{ form }}</pre>
</div>
</form>
</template>

<style scoped>
.form {
max-width: 500px;
margin: 0 auto;
}
.form-group {
margin-bottom: 15px;
}
label {
display: inline-block;
margin-right: 10px;
}
input[type="text"],
input[type="email"],
input[type="password"],
select,
textarea {
width: 100%;
padding: 8px;
margin-top: 5px;
}
textarea {
resize: vertical;
}
.error {
color: red;
font-size: 12px;
display: block;
}
.buttons {
display: flex;
gap: 10px;
margin-top: 20px;
}
button {
padding: 10px 20px;
}
button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.preview {
margin-top: 30px;
padding: 15px;
background: #f5f5f5;
}
pre {
white-space: pre-wrap;
}
</style>

自定义组件的 v-model

可以使用 model 选项自定义组件的 v-model 行为:

// MyInput.vue
<script setup>
defineProps({
modelValue: String
})

defineEmits(['update:modelValue'])
</script>

<template>
<input
:value="modelValue"
@input="$emit('update:modelValue', $event.target.value)"
/>
</template>
<!-- 使用 -->
<MyInput v-model="message" />

小结

本章我们详细学习了 Vue 表单绑定的完整内容:

  1. v-model 基本用法:文本、复选框、单选、下拉选择
  2. v-model 修饰符:.lazy、.number、.trim
  3. 表单验证:使用计算属性实现验证逻辑
  4. 完整表单示例:用户注册表单的实现
  5. 自定义 v-model:创建支持 v-model 的组件

练习

  1. 创建一个登录表单,包含用户名和密码
  2. 实现一个搜索过滤的输入框
  3. 创建一个问卷调查表单

准备好进入下一章,学习组件基础的内容了吗?