表单校验的工程化 同步、异步与错误提示
竹影三尺
4 分钟阅读
表单校验的工程化 同步、异步与错误提示
表单校验最怕两件事:规则散落在各处不好维护,以及异步校验(如查重)让提示时机乱掉。工程化做法是把校验抽象成"规则 + 调度 + 展示"三层,让新增字段只改配置,而不是在每个输入事件里写一堆 if。很多项目的表单越写越乱,根因就是把校验逻辑和 UI 事件耦合在了一起。
同步校验:纯函数规则
同步校验是纯函数,入参是值、出参是错误信息或真值,便于单测与复用:
const rules = {
email: [
v => (v && /\S+@\S+\.\S+/.test(v)) || '邮箱格式不正确',
v => (v && v.length <= 64) || '邮箱过长',
],
password: [
v => (v && v.length >= 8) || '密码至少 8 位',
],
};
把规则和字段解耦后,校验引擎只需遍历 rules[field],逐个执行直到出现第一条错误。这样校验逻辑可跨页面复用,也方便从中台统一下发规则,甚至让运营在后台改正则。同步校验的优点是即时、无网络依赖,适合格式、长度、必填这类本地可判定项。
异步校验:防抖与竞态
查重、查库存这类校验必须走接口,问题在"用户连输五字符,发五个请求,最先回来的结果却对应最早的输入"。用序列号丢弃过期结果:
let seq = 0;
async function validateEmail(v) {
const id = ++seq;
const exists = await checkEmailExists(v);
if (id !== seq) return; // 已有更新的校验,丢弃旧结果
setError(exists ? '邮箱已注册' : '');
}
还要加防抖:不要每次按键都打接口,等输入停顿约 300 毫秒再查,既省流量又避免提示频繁闪动。异步校验本身应标成"校验中"状态,防止提交时它还没回来就放行。常见 bug 是用户刚输入完立刻点提交,此时异步校验的 Promise 还没 resolve,表单却通过了——所以提交动作也必须 await 所有在途异步校验完成。边界:异步校验接口也有可能超时或报错,错误时要明确是"校验失败"还是"校验服务不可用",前者拦截提交,后者通常应放行以免误伤真实用户。
错误提示:时机与聚合
提示时机分三档:输入时只清错误不主动报错(避免边写边红让用户焦虑);失焦时做单字段校验;提交时跑全量校验。聚合层把所有字段错误收成一张映射表,提交按钮置灰条件直接读这张表是否为空,避免到处散落布尔变量。
文案要指明"怎么改"而不是只说"错误"。同样是邮箱非法,"请输入有效的邮箱,例如 name@example.com"比"邮箱格式错误"更能引导用户。多错误不要一次性弹窗轰炸,按字段就近展示,并聚焦到第一个出错的输入。对于长表单,还可以加一个"错误摘要"区,把所有问题列在顶部并支持点击跳转,无障碍体验也更友好。
边界
- 异步校验结果有网络延迟,提交瞬间要重新触发一次全量校验,不能信任上一次缓存结论。
- 规则里不要依赖组件实例,纯函数才能在服务端渲染或测试中独立运行;需要上下文(如"确认密码")时由引擎把整张表单值传入。
- 跨字段校验需要拿到多个值,引擎要支持把整张表单值传入校验函数,而非只能拿到单个字段值。
- 国际化场景下错误文案也要走字典,不能把中文硬编码在规则里。
经验上,表单校验的工程化投入产出比很高:一套规则引擎能覆盖站内几乎所有表单,后续加字段只是配置,且校验行为在全站保持一致。它把"什么时候报错、报什么、怎么聚合"这些容易出错的细节收口到一处。
浏览 0
点赞 0
评论 0