React 表单的秘密生活:用验证超能力控制不可控的东西!(React 第6天)
Source: Dev.to
为什么表单很重要:快速现实检查
想象一下:你正在构建一个出色的电子商务应用。用户需要注册、登录或使用支付信息结账。如果没有稳健的表单处理,数据会丢失,错误会随机出现,用户会离开。
在 React 中,表单不仅仅是 HTML 标签——它们是 动态、状态驱动的强大工具。我们会覆盖基础知识,但会加入真实场景的变化,例如在用户注册场景中,糟糕的验证可能会把无效的电子邮件泄漏到你的数据库中。听起来很有趣,对吧?
Source: …
受控组件 vs. 非受控组件:激烈的争论
在 React 中,表单元素(如 <input>)可以是 受控 或 非受控。
| 类型 | 描述 | 优点 | 缺点 |
|---|---|---|---|
| 受控组件 | React 完全掌控。你将输入的 value 绑定到 useState 状态,并在每次变化时通过 onChange 处理函数更新它。 | • 完全控制 • 易于验证 • 实时反馈 | • 代码稍多 • 大型表单可能出现性能问题 |
| 非受控组件 | 让 DOM 以传统方式处理。使用 refs(例如 useRef)在提交时获取值。 | • 对快速表单更简洁 • 重渲染更少 | • 难以进行实时验证 • “React 风格”较弱 |
场景 – 想象一个搜索框:
- 受控? 在用户输入时更新建议。
- 非受控? 在按下 Enter 时才获取查询——对基本需求可以,但会失去即时的交互魔法。
受控组件示例
import { useState } from 'react';
function ControlledInput() {
const [value, setValue] = useState('');
return (
<input
value={value}
onChange={(e) => setValue(e.target.value)}
placeholder="Type something..."
/>
);
}
非受控组件示例
import { useRef } from 'react';
function UncontrolledInput() {
const inputRef = useRef(null);
const handleSubmit = () => console.log(inputRef.current?.value);
return (
<>
<input ref={inputRef} placeholder="Type something..." />
<button onClick={handleSubmit}>Log Value</button>
</>
);
}
图示 – 可视化流程:
常见陷阱: 忘记在受控模式下处理 onChange——输入框会变成只读!
UX 小贴士: 对需要实时更新的功能(如密码强度指示器)使用受控组件。
表单处理:从输入到提交
处理表单主要涉及 事件 和 状态。从 <form> 元素开始,添加输入框,并在表单上挂上 onSubmit 处理函数。使用 e.preventDefault() 阻止默认的页面刷新。
实时示例 – 登录表单
import { useState } from 'react';
function LoginForm() {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const handleSubmit = (e) => {
e.preventDefault();
// API call here
console.log({ email, password });
};
return (
<form onSubmit={handleSubmit}>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="Email"
required
/>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="Password"
required
/>
<button type="submit">Login</button>
</form>
);
}
陷阱: 提交后未重置状态——用户会看到旧数据。
用户体验提升: 在提交期间添加加载指示器,营造专业感。
验证策略:别让错误数据破坏派对
验证是表单变得严肃的地方。常见策略包括:
- Inline – 用户输入时即时验证。
- On blur – 当字段失去焦点时进行验证。
- On submit – 用户尝试提交时进行验证。
简单的原生验证示例
import { useState } from 'react';
function SignupForm() {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [errors, setErrors] = useState({});
const validate = () => {
const newErrors = {};
if (!email.includes('@')) newErrors.email = 'Invalid email!';
if (password.length < 6) newErrors.password = 'Password too short!';
setErrors(newErrors);
return Object.keys(newErrors).length === 0;
};
const handleSubmit = (e) => {
e.preventDefault();
if (validate()) {
// Proceed with API call
console.log('Form is valid');
}
};
return (
<form onSubmit={handleSubmit}>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="Email"
required
/>
{errors.email && <p style={{ color: 'red' }}>{errors.email}</p>}
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="Password"
required
/>
{errors.password && <p style={{ color: 'red' }}>{errors.password}</p>}
<button type="submit">Sign Up</button>
</form>
);
}
验证实际效果的示意图:
陷阱: 过度验证 – 不要在每一次按键时都提醒用户。
UX 小贴士: 用红色显示友好的错误信息,并配以图标提升可读性。
与流行库的比较:原生 vs. 优点
为什么要重新发明轮子?库可以让生活更轻松。
| 库 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| Vanilla React | • 轻量,无依赖 • 对实现拥有完整控制 | • 需要更多样板代码 • 必须手动处理许多细节 | 小型到中型表单,学习基础概念 |
| Formik | • 开箱即用的状态、验证和提交处理 • 生态完善(Yup 集成) | • 增加打包体积 • API 有学习曲线 | 字段众多的复杂表单 |
| React Hook Form | • 最小化重新渲染,性能极佳 • API 简洁,能很好地配合已有 UI 库 | • 思维模型略有不同(默认非受控) | 大型表单、对性能要求高的应用 |
| Redux‑Form (legacy) | • 将表单状态集中在 Redux 中 • 适合已经大量使用 Redux 的项目 | • 已被更新的库取代 • 可能导致 Redux store 体积膨胀 | 已经在使用 Redux‑Form 的旧项目 |
要点
- 受控组件 能提供细粒度的控制,适合实时验证。
- 非受控组件 设置快速,但会牺牲实时洞察。
- 始终 阻止默认提交 并自行管理状态。
- 选择一种 验证策略,在用户体验和数据完整性之间取得平衡。
- 当表单变得复杂时,考虑使用 Formik 或 React Hook Form 等库来减少样板代码。
现在去构建坚固、用户友好的表单,让你的 React 应用闪耀吧! 🎉
Form Libraries Overview
Formik
- 处理状态、验证、touched/dirty 标记。
- 稍显笨重;通常与 Yup 搭配使用进行模式验证。
- 适用于拥有众多字段的 复杂表单。
React Hook Form
- 性能极佳;默认使用非受控输入。
- 简洁的验证 API;与原生 HTML 验证良好集成。
- 如果你对 hooks 不熟悉,可能需要一点学习曲线。
- 非常适合希望最小化重新渲染的 高性能应用。
Example
/* Formik */
import { Formik, Form, Field } from 'formik';
import * as Yup from 'yup';
/* React Hook Form */
import { useForm } from 'react-hook-form';
Scenario – 对于多步骤调查,React Hook Form 更出色,因为它需要的样板代码更少,重新渲染也更少。
观看对比视频: React Hook Form vs Formik (YouTube) (replace with a real video link)
可访问性最佳实践:面向所有人的表单
包容性很重要!始终为输入框配上正确的标签和 ARIA 属性,并正确管理焦点。
- 为每个输入框添加标签:
<label htmlFor="...">(或将输入框包裹在<label>中)。 - 错误处理:在无效字段上添加
aria-invalid="true"和aria-describedby="error-id"。 - 键盘导航:确保逻辑的 Tab 顺序,避免焦点陷阱。
场景 – 屏幕阅读器用户在填写联系表单时,如果缺少适当的 ARIA 标记会感到迷失。
可视参考
常见错误: 忽视错误信息的颜色对比度。
用户体验提示: 使用 WAVE 或 axe 等工具进行测试,以捕捉对比度问题。
实时示例与总结
自己动手试试 – 在 CodeSandbox 上的已验证注册表单:
打开 CodeSandbox (如有需要请替换为真实链接)
在实际应用中(例如编辑用户资料),通常会结合:
- 受控输入以实现即时 UI 反馈。
- 在失焦或提交时进行校验。
- 可访问的标签和 ARIA 属性。
- 当表单规模扩大时使用表单库(Formik 或 React Hook Form)。
这是一段 表单奇旅!你构建过最棘手的表单是什么?在下方留下你的故事吧。
敬请期待 第 7 天:路由与导航冒险。祝编码愉快! 🌟


