React 中的异步表单验证很难 — 这里有一种可预测的解决方案
Source: Dev.to
请提供需要翻译的具体文本内容,我将按照要求保留原始格式、代码块和链接,仅翻译正文部分。
核心问题:验证不具确定性
大多数表单解决方案基于事件进行验证,而不是基于状态快照。
示例异步验证器
async function validateEmail(value: string) {
return api.checkEmailAvailability(value);
}
想象用户快速输入:
- 输入
taken@example.com→ 异步请求 A 开始 - 更改为
john@example.com→ 异步请求 B 开始 - 请求 A 在请求 B 之后完成 ❌
UI 显示 “邮箱已被占用”(不正确)。
较旧的异步结果覆盖了更新的输入——这是一种经典的竞争条件。
问题 #1:异步验证竞争条件
目标: 只应考虑最新的验证结果。
许多表单库:
- 不跟踪异步验证的执行
- 不将验证绑定到稳定的值快照
- 允许过时的结果获胜
正确解决方案必须做到
- 跟踪异步验证尝试
- 忽略过时的异步结果
- 始终基于一致的值快照进行验证
没有这些,异步验证将永远不可预测。
Problem #2: 跨字段验证脆弱
实际表单经常需要一起验证多个字段,例如:
- 确认密码必须与密码匹配
- 结束日期必须晚于开始日期
- 只有在另一个字段启用时才需要某个字段
常见做法依赖于监听其他字段、手动重新验证或隐藏的依赖关系,这会引入难以调试的隐式行为。
显式跨字段验证
register("confirmPassword", {
validate: (value, values) =>
value !== values.password ? "Passwords do not match" : undefined,
});
- 没有魔法
- 没有自动重新验证
- 没有隐藏的依赖——只有可读的逻辑。
Problem #3:提交行为与更改不同
“验证在更改时有效,但提交时表现不同。”
这发生是因为许多库:
- 仅验证已触碰的字段
- 在提交时跳过未触碰的依赖字段
结果:出现意外的提交时错误。
解决方法的简单规则
在提交时,验证所有已注册的字段。永远如此。这使得提交行为可预测且正确。
解决方案:可预测的、异步优先的表单引擎
这些问题促使我们创建了 Formora,一个围绕严格原则构建的无头 React 表单引擎:
- 验证时机是显式的(
change|blur|submit) - 异步验证安全防止竞争条件
- 验证始终基于值快照运行
- 跨字段验证是显式的
- 不会自动重新验证依赖
使用 Formora 的真实示例
异步邮箱验证 + 确认密码
const form = useForm({
initialValues: {
email: "",
password: "",
confirmPassword: "",
},
validateOn: "change",
asyncDebounceMs: 500,
});
{
await new Promise((r) => setTimeout(r, 300));
if (value.includes("taken")) return "Email already taken";
},
})}
/>
value !== values.password ? "Passwords do not match" : undefined,
})}
/>
这为你提供了:
- 永不显示过时错误的异步验证
- 明确的跨字段逻辑
- 可预测的提交行为
- 干净、易于调试的代码
为什么其他开发者可以受益于 Formora
Formora 并不是要取代所有表单库;当你需要以下特性时,它表现出色:
- 正确的异步行为
- 明确的验证逻辑
- 类型安全、可预测的状态
- 在真实应用中可调试的表单行为
如果你的应用拥有异步验证、跨字段规则或复杂的提交逻辑,Formora 能提供稳固且可预测的基础。
最后思考
表单之所以变得困难,并不是因为它们本身复杂,而是因为验证的正确性常常被忽视。异步逻辑、字段之间的关系以及真实的用户行为都需要可预测性,而不是魔法。Formora 正是为了解决这些问题而明确且可靠地构建的。
有用的链接
- GitHub:
- npm: