别做工具库舔狗,我喂自己袋盐 ------------于晏 🤦♂️
聊聊表单方案的选择:原生表单和表单库到底该怎么选?可能对刚入门的兄弟们有点用,老鸟轻喷🤦♂️🤦♂️🤦♂️,为什么写这个低级得玩意,纯粹是因为需求变少,给自己找点事干,然后就是想加强一点对一些代码得自我思考能力,思考是思考了,但不多🤐

背景 😊
最近写了个账号删除表单,字段不多:邮箱、删除原因(单选)、两个确认复选框。验证逻辑也简单:必填项检查 + 邮箱格式校验。一开始纠结要不要用 React Hook Form 这类表单库,最后还是用了原生表单 + useState 实现。
倒不是说表单库不好,只是稍微思考了一下:我的删号这个场景入口在app,然后app点击得时候通过我给出得地址带过来加密得用户userid+请求token,然后我这边解析出userid和token+表单字段提交给后端,然后其实我的项目本身做的就是一些app内嵌h5功能,分享,协议,这次加了个邀请,然后目前没有涉及到很复杂得表单,用第三方得表单库,我认为是冗余得(这个纯看自己了,冗余不冗余也不知道各位彦祖怎么看),这种场景其实很常见,例:当你跟面试官介绍你自己使用得工具库时,你使用echarts 和react-echarts ,然后react-redux 和zustand得选择,为什么这样选择?理由是什么?业务决定?又或者性能?

为什么要纠结这个? 🤔
刚学 React 的时候总觉得不用个啥库就不专业,写表单必须上 React Hook Form 或者 AntD Form。后来写多了才发现,简单场景下强行用库反而会:
- 增加依赖体积(哪怕 10KB 也是额外加载)
- 多一层学习成本(register、handleSubmit 这些 API 得记)
- 调试变复杂(库的内部状态偶尔会让人摸不着头脑)
看看我这个删除账号表单的核心逻辑:
tsx
ini
// 状态管理
const [email, setEmail] = useState("");
const [reason, setReason] = useState("");
const [confirm1, setConfirm1] = useState(false);
const [confirm2, setConfirm2] = useState(false);
const [errors, setErrors] = useState({
email: "",
reason: "",
confirm1: "",
confirm2: "",
});
// 验证逻辑
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
const newErrors = { email: "", reason: "", confirm1: "", confirm2: "" };
let valid = true;
if (email && !/^[\w.-]+@[\w.-]+.\w+$/.test(email)) {
newErrors.email = "Invalid email format";
valid = false;
}
if (!reason) newErrors.reason = "Please tell us why you're leaving";
if (!confirm1) newErrors.confirm1 = "Required confirmation";
if (!confirm2) newErrors.confirm2 = "Required confirmation";
setErrors(newErrors);
if (valid) setShowModal(true);
};
这段代码虽然简单,但胜在:
- 零依赖,移植性强
- 逻辑直观,新手也能看懂
- 样式完全自定义(那个渐变色背景和自定义复选框轻松实现)
如果换成 React Hook Form,代码量可能差不多甚至更多,反而显得冗余。
什么时候该用表单库? 🤔
但如果遇到下面这些场景,我肯定会毫不犹豫用表单库:
-
字段数量多(比如注册表单有 10 + 字段)
- 原生需要写 N 个 useState 和 onChange,重复劳动
- 表单库一行 register 就能搞定
-
复杂验证(跨字段校验、异步校验)
- 比如 "密码强度校验"、"两次密码一致"
- 用 Zod 配合 React Hook Form,声明式写法秒杀手动 if-else
-
动态表单(动态添加 / 删除字段组)
- 比如 "紧急联系人" 可以添加多个
- 原生需要手动管理数组状态,表单库自带数组处理
看看这种场景下的代码对比(简化版):
jsx
ini
// 原生实现跨字段验证(确认密码)
const [password, setPassword] = useState('');
const [confirmPwd, setConfirmPwd] = useState('');
const [errors, setErrors] = useState({});
const validate = () => {
const newErrors = {};
if (password !== confirmPwd) {
newErrors.confirmPwd = "两次密码不一致";
}
setErrors(newErrors);
return Object.keys(newErrors).length === 0;
};
// React Hook Form + Zod实现
const schema = z.object({
password: z.string().min(6),
confirmPwd: z.string(),
}).refine(data => data.password === data.confirmPwd, {
message: "两次密码不一致",
path: ["confirmPwd"],
});
const { register, formState: { errors } } = useForm({
resolver: zodResolver(schema)
});
明显能感觉到复杂场景下表单库的优势 ------ 代码更简洁,逻辑更清晰。
我的选择标准(实战总结)
-
先看字段数量
- ≤5 个字段:优先原生(除非有特殊需求)
- ≥6 个字段:考虑表单库
-
再看验证复杂度
- 只有必填 / 简单格式校验:原生足够
- 有跨字段 / 异步校验:必须表单库
-
最后看样式需求
- 高度定制化设计:原生 + CSS(避免 UI 库样式冲突)
- 常规设计:可以用表单库 + UI 组件(如 MUI、Shadcn)
-
tips🤔🤷♂️
- 后台系统,复杂页面提交无脑工具库
- 简单表单,涉及到较少表单逻辑原生
回到我那个删除账号表单:
- 4 个字段(邮箱、原因、两个复选框)
- 验证只有必填和邮箱格式
- 样式有渐变背景、自定义复选框
- 结论:原生实现是最优解 😴
我的表单展示

常见误区
-
认为用表单库就是高级
- 其实过度使用库会导致代码冗余,增加维护成本
-
原生表单一定比库性能好
- 复杂表单场景下,表单库(如 React Hook Form 基于非受控组件)性能反而更好
-
样式和逻辑必须绑定
- 最好分开处理:用 Tailwind/CSS Modules 写样式,用状态管理工具处理逻辑
-
~~ 追求一刀切方案~~ (思考)
- 项目里完全可以混合使用:简单表单用原生,复杂表单用库 (彦祖们怎么看这个问题,我其实是喜欢统一)
最后想说的

写代码和做事一样,讲究 "恰到好处"。不用盲目追求新技术新工具,也不能固守旧方法不变。
判断一个方案好不好,不是看它多先进,而是看它能不能用最少的代码解决问题,同时保证可维护性和用户体验。
就像我这个删除账号表单,用原生实现可能在某些人看来不够 "高级",但对当前场景来说,它就是最合适的方案。
从一开始纠结用不用库,到分析场景找到最优解,这个过程本身比结论更有价值吧。毕竟编程这东西,没有绝对的对错,只有适合不适合🤐🤐🤐