表单与异步校验
表单与异步校验 · 阅读后完成下方实验与检查题,再用「标记完成」记录进度。
学习目标
读完你能构建一个带异步唯一性检查的多字段表单:输入防抖后校验、只有最后一次校验结果生效、提交状态与字段错误清晰分离、字段依赖变化时主动触发重校验。
问题场景
注册表单的用户名要查重(异步,几百毫秒)。第一版的 bug 图鉴:快速输入 "tom" 再改成 "tommy",先发的 "tom" 校验结果后到,页面显示「tom 可用」——但输入框里已经是 tommy;校验还在进行,提交按钮却能点;改了密码,确认密码的错误提示还停留在旧规则上。这些症状共享一个根因:异步校验结果与新输入之间,没有「谁新谁旧」的约定。
核心解释
三件事把表单校验做稳。
**一、请求序号守卫:只有最新输入的校验结果能落地。**结果必须带上「它校验的是哪个值」,落地前先比对:
useEffect(() => {
if (!username) { setUsernameCheck(null); return; }
let alive = true;
const timer = setTimeout(() => {
checkUsername(username).then((ok) => {
if (alive) setUsernameCheck({ value: username, ok }); // 带上它校验的值
});
}, 300);
return () => { alive = false; clearTimeout(timer); };
}, [username]);
// 判定时:
const checkValid = check && check.value === username && check.ok;
value 不相等就是过期结果,不显示——这一道比对挡住所有乱序问题,与请求竞态的保护同一血统。
**二、提交状态独立于校验状态。**校验进行中(checking)与提交中(submitting)是两条线;提交按钮在「校验中 / 有错误 / 提交中」三种情况下禁用,并让用户知道在等什么。
**三、字段依赖主动重校验。**确认密码依赖密码、结束日期依赖开始日期:把依赖写进 useEffect 的依赖数组,被依赖字段一变,依赖字段的校验结果要么重算、要么立即标脏——让机制而不是记性来保证:
const canSubmit = checkValid && password && !confirmError && !submitting;
示例拆解
实验区注册表单三个字段:用户名(异步查重,400ms)、密码、确认密码:
- 输入用户名,停手 300ms 后出结果;快速改写时,旧结果落地前被丢弃,页面显示「已丢弃过期校验」计数;
- 输入被占用的名(如 admin),提示「已被注册」并阻止提交;
- 改密码后,确认密码的「与密码不一致」提示立即刷新(确认密码是派生校验);
- 提交:校验齐全才可点,提交中按钮显示「提交中…」并防重复;
- 验证:快速输入 → 改写 → 立即提交,确认提交用的是最新值且无旧提示残留。
动手任务
打开本课实验区:
- 把防抖调成 1000ms,快速输入后立即点提交:体验「校验中禁止提交」,并在按钮旁写上等待原因。
- 去掉
check.value === username的比对,复现「旧结果覆盖新输入」,记录复现步骤,再恢复。 - 新增「邮箱(可选):填了就查格式与唯一性」,复用同一套守卫模式。
常见错误
- 校验结果不带输入值就落地:乱序必现「张冠李戴」;结果必须与触发它的输入绑定。
- 提交与校验共用一个 loading:用户不知道在等查重还是在等提交;两条状态线分开。
- 只在 blur 时校验:输入过程中依赖字段已经变了,提示还是旧的;依赖变化时标脏或重校。
- 错误提示不随输入清除:改对了还挂着旧错误;输入变化时清除或重算本字段错误。
检查与复盘
自查清单:
- 能解释「过期校验结果」如何被 value 比对挡住
- 提交禁用的三种原因(校验中 / 有错误 / 提交中)都能被用户看见
- 字段依赖(密码 ↔ 确认密码)的重校验由机制驱动,验证过
参考资料
- React 官方文档:input 组件 —— 受控输入的约定
- React 官方文档:form 组件 —— 表单提交与 Action
- React 官方文档:管理状态 —— 多字段状态的组织
读完这一课了?
标记完成后,工作台会安排接下来的学习。