3.4 事件与表单绑定
本章目标: 让输入框与状态保持一致,处理提交、防止刷新,给出清晰的错误反馈。
一个完整的本地表单
src/App.vue
<script setup lang="ts">
import { ref } from 'vue';
const title = ref('');
const error = ref('');
const tasks = ref<{ id: number; title: string }[]>([]);
let nextId = 1;
function addTask() {
const value = title.value.trim();
if (!value) {
error.value = '请输入任务名称';
return;
}
if (value.length > 100) {
error.value = '任务名称不能超过 100 个字符';
return;
}
tasks.value.push({ id: nextId++, title: value });
title.value = '';
error.value = '';
}
</script>
<template>
<main>
<form @submit.prevent="addTask" novalidate>
<label for="task-title">任务名称</label>
<input id="task-title" v-model.trim="title" maxlength="100"
:aria-invalid="Boolean(error)" aria-describedby="title-error" />
<button type="submit">添加</button>
<p id="title-error" role="alert">{{ error }}</p>
</form>
<ul><li v-for="task in tasks" :key="task.id">{{ task.title }}</li></ul>
</main>
</template>
novalidate 关闭浏览器自动弹出的验证提示,便于观察自定义校验。若使用原生 required 校验,也要为业务和 API 层保留独立检查。
事件与修饰符
@submit.prevent 会调用 preventDefault(),阻止表单默认导航。常用修饰符:
| 修饰符 | 意义 | 示例 |
|---|---|---|
.prevent | 阻止默认行为 | 表单提交 |
.stop | 阻止事件冒泡 | 行内按钮不触发行点击 |
.once | 只监听一次 | 一次性初始化动作 |
.enter | 限定 Enter 键 | @keyup.enter="search" |
需要原生事件时可以使用 $event:@click="handleClick($event)"。TypeScript 函数里将它标注为 MouseEvent。
v-model 与控件类型
| 控件 | 绑定状态 |
|---|---|
| 文本输入框 | 字符串 |
| 单个 checkbox | 布尔值 |
| 多个同组 checkbox | 选中值数组 |
| radio / select | 选中项的值 |
| select multiple | 多个选中值的数组 |
常用修饰符 .trim 去掉首尾空白,.number 尝试数字转换,.lazy 改为在 change 时同步。.number 在空输入等场景不保证一定返回数字,仍要验证范围和类型。
脚本中已定义 filter 的选择框片段
<select v-model="filter">
<option value="all">全部</option>
<option value="pending">未完成</option>
<option value="done">已完成</option>
</select>
异步提交状态
接入 API 后需要处理重复操作。脚本里同时检查 saving 并禁用按钮:
createTask 已定义时的提交片段
const saving = ref(false);
async function submit() {
if (saving.value) return;
saving.value = true;
try {
await createTask(title.value.trim());
title.value = '';
error.value = '';
} catch (cause) {
error.value = cause instanceof Error ? cause.message : '保存失败';
} finally {
saving.value = false;
}
}
finally 确保失败后也能再次提交。提交失败时保留输入,用户才能修改或重试。服务端仍需为重要操作提供幂等或重复请求保护。
练习
给表单添加一个优先级 select,值为 normal 和 high。任务新增后同时展示名称和优先级。
参考答案
脚本增加 const priority = ref<'normal' | 'high'>('normal'),给任务模型增加同类型的 priority。新增时把 priority.value 保存到任务对象;模板中用 v-model="priority" 绑定 select,选项值分别为 normal 和 high。
这是一次模型、输入和显示同步修改的练习。接入 API 时 DTO 也要一起更新。