8.2 表单验证:分层校验与提交时序


文档摘要

8.2 表单验证:分层校验与提交时序 本节摘要:验证分两层——字段级即时反馈(blur 或 input 时机)与提交级整体把关(async-validator 是事实标准)。本节从手写验证讲到规则库集成,覆盖错误信息组织、错误回填、防重复提交与提交竞态这些真实工程里躲不开的问题。 学习目标 阅读完本节,你应当能够: 设计字段级与提交级的双层验证结构; 手写声明式规则并理解 async-validator 的规则形态来源; 处理提交时序:防重、竞态取消、服务端错误回填; 决定"何时校验"的时机策略而非一律实时校验。

8.2 表单验证:分层校验与提交时序

本节摘要:验证分两层——字段级即时反馈(blur 或 input 时机)与提交级整体把关(async-validator 是事实标准)。本节从手写验证讲到规则库集成,覆盖错误信息组织、错误回填、防重复提交与提交竞态这些真实工程里躲不开的问题。

学习目标

阅读完本节,你应当能够:

  1. 设计字段级与提交级的双层验证结构;
  2. 手写声明式规则并理解 async-validator 的规则形态来源;
  3. 处理提交时序:防重、竞态取消、服务端错误回填;
  4. 决定"何时校验"的时机策略而非一律实时校验。

一、验证的时机分层

校验时机的决策路径

校验时机的决策路径

四个常见时机,各有代价与体验:

时机 触发点 体验 适用
input 实时 每键一次 反馈最快但吵 用户名重复等远程检查(要防抖)
blur 失焦 离开字段 安静且及时 大多数字段的默认选择
submit 提交 点提交时 一次看全错 密码强度、跨字段一致性
变化防抖 停顿几百毫秒 平衡 长文本、格式类检查

一刀切实时校验是体验反模式——用户打完名字的第一个字就被标红"至少两个字",这种界面谁都暴躁。我的默认策略:短字段 blur 校验,联动与跨字段校验放提交时,远程检查一律防抖加失焦兜底。

二、手写声明式验证:看清规则的本质

先不用库,把结构写对:

const rules = { username: [ { required: true, message: '请输入用户名', trigger: 'blur' }, { min: 2, max: 20, message: '长度 2 到 20 个字符', trigger: 'blur' }, { validator: (rule, value) => /^[\w-]+$/.test(value), message: '仅限字母数字下划线连字符', trigger: 'blur' } ], age: [ { required: true, message: '请输入年龄', trigger: 'blur' }, { validator: (r, v) => v >= 0 && v <= 150, message: '年龄超出合理范围', trigger: 'blur' } ] }; const errors = reactive({}); function validateField(field) { errors[field] = rules[field] .filter((rule) => { if (rule.validator) return !rule.validator(rule, form[field]); if (rule.required) return !String(form[field]).trim(); if (rule.min) return String(form[field]).length < rule.min; return false; }) .map((rule) => rule.message); }
<input v-model="form.username" @blur="validateField('username')"> <p v-for="msg in errors.username" :key="msg" class="error">{{ msg }}</p>

这套"规则数组 + 触发时机 + 消息"的形态,正是 Element Plus、Ant Design Vue 等组件库表单验证的通用形态——它们内置的都是 async-validator 这个库。理解手写版,组件库的 rules 属性就不再是黑盒。

三、集成组件库的验证

Element Plus 的表单,同一套规则换个挂法:

<template> <el-form :model="form" :rules="rules" ref="formRef"> <el-form-item label="用户名" prop="username"> <el-input v-model="form.username" /> </el-form-item> <el-button @click="submit">提交</el-button> </el-form> </template> <script setup> import { ref, reactive } from 'vue'; const formRef = ref(); const form = reactive({ username: '' }); const rules = { username: [ { required: true, message: '请输入用户名', trigger: 'blur' } ] }; async function submit() { await formRef.value.validate(); // 整体校验,不通过会 reject 并显示全部错误 await api.save(form); } </script>

三个易错点:form-item 的 prop 必须与 model 里的字段名一致(错误定位靠它对应);整体校验要 await 它返回的 Promise,别只调用不等待;自定义 validator 返回 Promise 即支持异步规则(重名检查就在这里做,记得防抖)。

四、提交时序:三个躲不开的问题

防重复提交——网络慢时用户连点,产生两条订单:

const submitting = ref(false); async function submit() { if (submitting.value) return; // 第二次点击直接挡掉 submitting.value = true; try { await formRef.value.validate(); await api.save(form); } finally { submitting.value = false; // 无论成败都解锁 } }

按钮同时绑 :disabled="submitting",视觉与逻辑双保险。

竞态取消——表单里带联想搜索时,快速输入触发多个请求,慢的先发后到,覆盖了新结果。解法是序号守卫:

let searchSeq = 0; async function searchRelated(keyword) { const seq = ++searchSeq; const res = await api.suggest(keyword); if (seq !== searchSeq) return; // 过期响应直接丢弃 suggestions.value = res.list; }

服务端错误回填——客户端校验拦不住全部(唯一性、余额),提交被拒后要把服务端错误映射回字段:

async function submit() { try { await api.save(form); } catch (err) { if (err.fieldErrors) { for (const [field, messages] of Object.entries(err.fieldErrors)) { errors.value[field] = messages; // 结构与本地验证一致,展示复用 } } } }

错误信息结构统一成"字段到消息数组"的映射,本地与远端错误就能共用一套展示,这是表单组件设计里最值钱的一个约定。

五、大表单的组织:分步与分区

字段几十个的表单(开户、理赔)要分区甚至分步。分步验证的要点是按步校验而非全量校验:第一步只验第一步的字段(把 rules 按步拆组),最后一步提交时做全量兜底——用户不必为最后一页的错误回头改第一页。分区保存(草稿)则把"部分字段合法"定义为合法,提交时才收紧,这需要与服务端约定草稿接口的宽松校验规则。

本节要点回顾

再强调一个和团队协作有关的实践:验证规则要成为前后端的共享契约,而不是前端的自娱自乐。同样的长度限制、格式要求、取值范围,如果只在 Vue 里写一遍,后端再散着写一遍,两边迟早漂移——某次需求改动只改了一侧,另一侧就成了摆设或误伤。可行做法是把规则定义抽出为一份独立的共享描述(字段、约束、消息),前端用它驱动表单校验,后端用它做入库前校验,漂移在源头就被锁住。这属于第 11 章分层规范的延伸,放在表单语境下提出来,是因为它最容易在表单模块里落地。

  • 时机分层:短字段 blur、跨字段提交时、远程检查防抖,实时校验慎用;
  • 规则形态:规则数组加触发时机加消息,手写版就是组件库 rules 的骨架,底层多为 async-validator;
  • 三个约定:form-item 的 prop 对应字段名、整体校验要 await、异步验证器返回 Promise;
  • 提交三防:防重复(状态锁加禁用)、防竞态(序号守卫)、错误回填(统一字段消息结构);
  • 大表单:按步分组校验,草稿宽松提交收紧,规则与服务端对齐。

表单这条反向通道走完,下一章回到主线:让整条渲染管线跑得更快。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U