ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

表单验证完整指南:从原生原理到工程化落地

表单验证完整指南:从原生原理到工程化落地 表单验证完整实现从原生原理拆解到工程化落地表单验证这事说简单是真简单——一个required属性、一段正则、一个if判断就完事。但说复杂也真复杂我接手过的项目里因为表单验证没做好导致线上出事故的案例一只手数不过来。有的是用户提交了非法数据直接打爆后端接口有的是前后端校验规则不一致导致用户填了半天最后提交失败还有的是密码校验写得稀烂用户改个邮箱都要触发一堆诡异的联动报错。这篇文章我不打算讲那种5分钟学会表单验证的入门教程而是把我这些年在前端项目里沉淀下来的表单验证完整实现方案拆开揉碎讲清楚。从最初的原生实现到规则数据结构的设计再到第三方库的工程化选型最后把实战中遇到的那些坑一个个列出来。无论是刚入门想建立完整认知的新手还是已经在项目里被表单折磨得焦头烂额的老手这篇文章应该都能给你一些启发。1. 表单验证的本质与设计思路拆解1.1 验证的四个层次合法性、有效性、一致性、安全性在做任何表单验证之前必须先想清楚一个问题你到底在验证什么很多同学上来就写正则邮箱正则写一个、手机号正则写一个然后完事大吉。这种思路其实是把表单验证简单化了真正的表单验证至少包含四个层次。合法性是最基础的就是验证用户输入的内容在格式上是否符合预期。邮箱要有符号手机号要是 11 位数字日期要符合YYYY-MM-DD的格式这些属于合法性验证。我在实际项目里见过太多因为正则写得太宽松或者太严格导致的问题宽松了垃圾数据进去了严格了把正常用户挡在外面。有效性比合法性高一个层次它验证的是这个值在业务上能不能用。举个例子用户注册时填写的用户名格式合法了比如4到16位字符但这个名字已经被别人注册了这就是有效性问题。再比如用户提交的优惠券码格式完全正确但已经过期了这也是有效性验证。有效性验证通常需要和业务逻辑交互甚至需要调用后端接口来判断。一致性验证则比较特殊它关心的是多个字段之间的关系。经典的场景是确认密码要和密码一致还有所在城市要根据所在省份联动结束日期不能早于开始日期。这类验证用简单的单字段校验是做不了的需要设计成跨字段的规则。安全性验证是很多人最容易忽略的。我在安全应急响应里看到过不少因为表单验证不到位被攻击的案例。比如用户在评论框里提交了恶意代码比如用户在上传头像时传了一个伪装成图片的木马文件比如在搜索框里输入了超长的字符串直接把后端内存打爆。前端表单验证在安全性这个层面能做的有限但至少要做好长度限制、内容过滤、特殊字符处理这些基础工作。想清楚了这四个层次再去设计你的验证逻辑就会比拿到需求就开写要靠谱得多。1.2 前端验证与后端验证的分工体验归前端安全归后端这是我在团队里反复强调的一个原则前端验证是为了用户体验而生后端验证才是安全性的守门员。前端的验证可以跳过改掉页面的 JS 或者直接用 curl 提交所以它永远不能替代后端验证。前端验证的目标是在用户填表的那一刻给出即时反馈让用户在提交之前就知道自己填得对不对免去了提交后被弹回来重新填写的痛苦。后端验证的目标则是防御一切非法数据不管数据是从哪个渠道来的都必须在服务端进行严格的校验确保只有合法数据才能进入业务逻辑和数据库。在工程实践中我推荐的做法是前后端共享一套验证规则的定义。最简单的方案是后端定义好校验规则通过接口下发前端渲染表单时动态应用这些规则。复杂一点的方案是用 JSON Schema 作为一种标准前端校验引擎和后端校验引擎都基于同一份 Schema 执行校验。这样设计前后端规则永远是一致的不会出现前端说手机号合格、后端却拒绝提交的尴尬。1.3 验证时机策略onChange、onBlur 与 onSubmit 的三段式设计验证时机是表单体验的分水岭。用好了用户觉得这个表单很聪明用不好用户觉得这个表单在找茬。我在项目里的默认策略是三段式未触碰过的字段不校验触碰后离开时做单字段校验onBlur提交时做全量校验onSubmit校验通过后如果用户继续修改则实时校验onChange。这个策略的核心思想是不打扰。用户刚开始填表时表单是安静的不会因为在邮箱框里打了一个字符就弹出一排红字。但当用户填完一个字段准备进入下一个字段时就需要给出及时反馈告诉用户刚才填的内容是否正确。最后在提交时把所有字段一次性校验一遍已经标红的地方保持标红没标红的地方如果有错误也要立刻呈现出来。onChange 实时校验是个双刃剑用得好体验极佳用不好就会出现用户在输入过程中报错信息疯狂跳变的糟糕体验。我这里的经验是onChange 校验只在字段已经被标记为已提交过之后才启用也就是用户点过提交按钮之后如果表单还有错误此时每个字段的输入变化都要实时重新校验让用户看到错误在一点点消失这种正向反馈对完成表单非常有帮助。2. 原生实现一套可用的表单验证方案2.1 规则声明式设计把校验逻辑与业务逻辑分离如果你只是需要一个通用的校验库直接引入第三方即可比如 async-validator 或者 yup。但如果你的业务比较复杂或者你想真正理解表单验证的原理那我强烈建议你至少手写一遍核心逻辑。我手写方案时首先做的事情是定义一套声明式的规则描述结构。所谓声明式就是让开发者通过配置来描述这个字段该怎么校验而不是在 JS 里写满if...else...。const rules { username: [ { required: true, message: 请输入用户名, trigger: blur }, { min: 4, max: 16, message: 用户名长度需在4到16个字符之间, trigger: blur }, { pattern: /^[a-zA-Z0-9_-]$/, message: 用户名只能包含字母、数字、下划线和连字符, trigger: blur } ], email: [ { required: true, message: 请输入邮箱地址, trigger: blur }, { type: email, message: 请输入正确的邮箱地址, trigger: blur } ], password: [ { required: true, message: 请输入密码, trigger: blur }, { min: 8, max: 32, message: 密码长度需在8到32个字符之间, trigger: blur }, { validator: (rule, value) { if (!/[a-z]/.test(value) || !/[A-Z]/.test(value) || !/\d/.test(value)) { return Promise.reject(密码需包含大小写字母和数字); } return Promise.resolve(); }, trigger: blur } ], confirmPassword: [ { required: true, message: 请再次输入密码, trigger: blur }, { validator: (rule, value, callback, formData) { if (value ! formData.password) { return Promise.reject(两次输入的密码不一致); } return Promise.resolve(); }, trigger: blur } ] };这种结构的好处是非常直观一个字段对应一个规则数组每条规则包含校验条件required、min、max、pattern、validator和错误信息以及触发时机。规则是纯数据可以在任意地方被引用甚至可以序列化传输到后端共享。2.2 校验引擎实现核心循环与错误收集机制有了规则定义就需要一个校验引擎来执行。这个引擎的核心逻辑其实很简单遍历表单的所有字段对每个字段按顺序执行它的规则数组一旦遇到校验失败就记录错误信息并且可以选择是立即终止该字段的后续规则还是继续执行——我推荐的方式是默认终止因为如果第一条规则就已经失败了后面的规则大概率也是徒劳用户只需要看到最重要的那一条错误即可。真正的校验引擎代码我简化后大概是这样的class FormValidator { constructor(rules, formData) { this.rules rules; this.formData formData; this.errors {}; } async validateField(fieldName, trigger) { const fieldRules this.rules[fieldName]; if (!fieldRules) return true; const value this.formData[fieldName]; for (const rule of fieldRules) { if (trigger rule.trigger rule.trigger ! trigger) continue; const isValid await this.executeRule(rule, value, fieldName); if (!isValid) { this.setError(fieldName, rule.message); return false; } } this.clearError(fieldName); return true; } async validateAll() { const fields Object.keys(this.rules); let isValid true; for (const field of fields) { const fieldValid await this.validateField(field); if (!fieldValid) isValid false; } return isValid; } async executeRule(rule, value, fieldName) { if (rule.required (value undefined || value null || value )) { return false; } if (rule.min typeof value string value.length rule.min) return false; if (rule.max typeof value string value.length rule.max) return false; if (rule.pattern typeof value string !rule.pattern.test(value)) return false; if (rule.validator) { try { await rule.validator(rule, value, null, this.formData); return true; } catch (e) { // validator 中 reject 的错误信息可覆盖默认 message if (typeof e string) { this.setError(fieldName, e); } return false; } } return true; } setError(field, message) { if (!this.errors[field]) this.errors[field] []; this.errors[field].push(message); } clearError(field) { delete this.errors[field]; } getFieldError(field) { return this.errors[field] ? this.errors[field][0] : ; } }这套引擎的设计可以支撑从简单必填到异步校验到跨字段联动校验的大部分场景。本质上它是把校验逻辑抽象成了一个个独立的规则单元然后按顺序执行、聚合结果。2.3 错误信息呈现交互不骚扰、不遗漏、可恢复交互体验是表单验证的灵魂。同一个校验逻辑不同的呈现方式给用户的感受完全不同。我的默认方案是在每个表单字段下方预留一行错误信息的位置字体用红色或者符合设计规范的颜色字号稍小字段边框在出错时变红通过aria-describedby属性建立错误信息与输入框的关联方便读屏软件播报。这里有几个值得注意的细节。第一错误信息不能挤占布局空间否则表单会出现跳动的视觉问题——字段下方一会儿有红字一会儿没有整个页面跟着上下抖动。我的方案是始终预留一行空间没有错误时显示为空白有错误时填入文案保证布局稳定。第二错误信息的措辞也很重要请输入有效的邮箱地址 比 邮箱格式错误 更能让用户理解该怎么做这种微小的文案差异对转化率的影响有时候比你想象的大得多。第三错误信息最好支持定位——用户点击提交按钮后如果有未通过的字段页面要自动滚动到第一个错误字段并聚焦让用户知道问题出在哪里。3. 工程化方案的选型与实践从原生到第三方库的迁移路径3.1 主流验证方案对比async-validator、yup、zod、joi自己手写验证引擎是理解原理的最好方式但在实际项目落地时我通常不会直接使用自己写的裸方案因为工程化需要的远不止校验一下这么简单。项目要求你处理异步校验的竞态、错误信息的国际化、与组件库的深度集成、开发时的类型提示这些如果全部自己实现要投入的精力会远超预期。目前主流的前端表单验证方案大致有这几类。async-validator是蚂蚁金服开源的校验库antd 和 Element UI 这些组件库内部都在用API 风格和我在上面写的声明式规则很像学习成本低文档齐全。yup是纯函数式的 schema 校验库API 设计非常优雅可以直接和react-hook-form配合使用写起来有一种声明即所得的畅快感。zod则是近几年崛起的新贵最大的卖点是 TypeScript 类型推导极其强大一个 schema 既能当运行时校验器又能当静态类型定义真正做到了一次定义、处处使用。joi是老牌的 Node.js 校验库生态成熟但在浏览器端体积偏大更适合服务端用。我用一张表把它们的核心差异列出来方案依赖体积TypeScript 支持异步校验生态集成适用场景async-validator较小中等支持但较原始与 antd 深度融合中后台、MVC 框架yup中等较好原生支持与 react-hook-form 搭配极佳React 技术栈为主zod小极强类型推导原生支持与 tRPC、react-hook-form 均有适配全栈 TS 项目、对类型安全有执念的团队joi较大好原生支持服务端生态成熟纯 Node 端、API 参数校验如果你在项目里有任何框架层面的限制比如你的组件库是 antd那 async-validator 就是最顺滑的选择因为它已经被内置在各种组件库内部不需要再引入一套新东西。如果你的项目是 React 技术栈且没有历史包袱我推荐react-hook-form配合zod的组合这套方案我用下来在体验和性能上都很满意。3.2 工程化配置一个 React 表单验证的完整案例这里我给出一个 React react-hook-form zod 的完整配置文件这是一个注册页面包含用户名、邮箱、密码和确认密码并且集成了异步的用户名是否已被注册校验。import { useForm } from react-hook-form; import { zodResolver } from hookform/resolvers/zod; import { z } from zod; // 定义表单数据类型 type RegisterForm { username: string; email: string; password: string; confirmPassword: string; }; // 定义校验规则 const registerSchema z.object({ username: z .string() .min(4, 用户名至少4个字符) .max(16, 用户名最多16个字符) .regex(/^[a-zA-Z0-9_-]$/, 用户名只能包含字母、数字、下划线、连字符), email: z.string().email(请输入有效的邮箱地址), password: z .string() .min(8, 密码至少8个字符) .regex(/[a-z]/, 密码需包含小写字母) .regex(/[A-Z]/, 密码需包含大写字母) .regex(/\d/, 密码需包含数字), confirmPassword: z.string() }).refine((data) data.password data.confirmPassword, { message: 两次输入的密码不一致, path: [confirmPassword] }); function RegisterForm() { const { register, handleSubmit, formState: { errors, isSubmitting } } useFormRegisterForm({ resolver: zodResolver(registerSchema), defaultValues: { username: , email: , password: , confirmPassword: } }); // 异步校验用户名是否可用 const validateUsernameAvailable async (username: string) { if (!username) return true; // 模拟接口校验实际项目中替换为真实请求 const res await fetch(/api/check-username?name${encodeURIComponent(username)}); return res.ok; }; return ( form onSubmit{handleSubmit((data) console.log(提交数据, data))} div input {...register(username, { validate: validateUsernameAvailable ? undefined : undefined })} / {errors.username p{errors.username.message}/p} /div div input {...register(email)} / {errors.email p{errors.email.message}/p} /div div input typepassword {...register(password)} / {errors.password p{errors.password.message}/p} /div div input typepassword {...register(confirmPassword)} / {errors.confirmPassword p{errors.confirmPassword.message}/p} /div button typesubmit disabled{isSubmitting} {isSubmitting ? 提交中... : 注册} /button /form ); }这个方案的优点在于校验规则被声明为一个zodschema和组件逻辑彻底分离可以单独进行单元测试zodResolver自动完成数据校验并映射到errors对象代码量大幅减少类型安全上useFormRegisterForm让所有字段和取值都具备完整的类型提示改字段名时会让所有引用它的地方统统报错再也不用手动查找替换。3.3 手写方案还是引入第三方库我在什么情况下怎么选关于手写还是引库每个团队都有不同的考量。我这里基于实际项目经验给出一个选择维度。如果项目只有一两个表单且字段简单最多几个必填加一个邮箱格式我会选择手写一套轻量的验证逻辑配合组件库的 rules 属性控制在 50 行以内解决问题不需要引入任何额外的依赖。如果项目表单较多但形态类似都是几个字段、几个校验规则我会封装一个通用的表单验证器作为内部基础组件沉淀下来这既是前面我分析的那套原生引擎的工程化升级版也为后续项目建立统一的基础设施。如果项目里表单形态多种多样有动态表单项、有跨字段联动、有异步校验、有无障碍需求那么直接引入成熟的第三方方案是唯一合理的选择。在 React 生态我用 react-hook-form zod在 Vue 生态我用 VeeValidate yup在 antd 项目里我用 async-validator。这些方案在性能、可维护性、社区案例上都已经过了大量生产环境的考验。我的建议是不要为了炫技而手写也不要不加分析就引库。表单验证的本质是规则引擎而规则引擎的正确性是靠大量测试和真实场景打磨出来的第三方库的价值不仅仅是省代码更是它的测试覆盖和社区踩坑经验。你引入的不只是一段代码而是一群人的经验。4. 实战中高频踩坑问题与排查技巧实录4.1 前后端规则不一致引发的幽灵问题先说一个真实踩坑案例。之前做一个电商项目前端用正则校验手机号是/^1[3-9]\d{9}$/可以正常通过。但后端校验逻辑写的是验证 11 位数字且以 1 开头。用户在后端测试时发现有个手机号19912345678前端能过后端能过但后面发现这个号段不是有效号段被运营商限制于是所有走了这套规则的用户在真正下单时全部失败。最后排查发现是前端规则比后端多过滤了一些号段而规则又不透明各改各的导致两边维护出了断层。这个问题的最佳解法就是我在前面提到过的——前后端共享一套规则。如果无法做全端共享至少要在后端保留完整的校验前端只做基础校验和体验增强永远不要让前端规则成为数据的最终判断者。我后来在项目里推动了一套规范任何字段的校验规则都以服务端接口文档为准前端的每个校验规则必须标注对应的文档编号两个版本出现分歧时直接以文档和接口实际行为为准。4.2 异步校验的竞态问题防抖与请求顺序控制异步校验是表单验证里最容易被忽略又最容易出 bug 的环节。比如用户名是否唯一需要防抖后发送请求给后端判断。防抖本身还好解决真正麻烦的是请求竞态。用户在用户名框里先输入了 nice又快速改成 nonice前面两个请求都发了出去。如果第一个请求比第二个请求返回还慢那么第二次校验结果会被第一次覆盖导致页面显示该用户名已被注册哪怕 nonice 是合法的。解决方案有两个层面。第一个层面是请求层面的竞态控制最简单的实现是使用一个递增的 request id每次发起新请求时记录当前 id只有在响应时 id 仍然是最新的才更新校验结果否则丢弃响应。第二个层面是引入防抖在用户停止输入 300-500 毫秒后才发起请求可以大幅减少请求频率。let currentCheckToken 0; const checkUsername (value) { const token currentCheckToken; debounce(async () { try { const result await fetch(/api/check/${value}); if (token currentCheckToken) { applyResult(value, result); } } catch (e) { if (token currentCheckToken) { handleError(e); } } }, 300)(); };这个当前 token模式我在多个项目里反复使用简单可靠可以处理所有类似的异步竞态问题。如果你的项目里已经有 RxJS也可以考虑用它来解决但为了一个小小的竞态问题引入 RxJS 我一般不建议。4.3 中文输入法组合态问题拼音输入一半就触发了校验输入 cheng 拼音准备选字成 程 时输入框里的 value 会在拼音阶段就变成 cheng如果此时有正则校验规则要求只能输入中文sale 会立刻报错用户体验直接炸裂。这个问题在手机和 PC 上都会出现PC 端前端一般使用compositionstart和compositionend事件来解决。核心思路是在输入法组合状态时暂停所有基于值变化的校验onChange 校验等组合状态结束后再触发校验。在 React 中大部分表单校验库已经内置了对这个问题的处理但如果你用的是原生手写的校验方案需要注意自己加上这段逻辑。我在项目里的默认策略是给输入框绑定compositionstart和compositionend事件用标志变量记录当前的组合状态校验函数在组合状态期间直接跳过非必选校验。这个细节看着不起眼但对于面向中文用户的产品是必须处理的基础问题。4.4 无障碍与可访问性仅仅变红是不够的表单验证的错误提示不能只依赖红色边框和文字颜色。对色弱、色盲以及使用读屏软件的用户来说如果错误只靠颜色传达等于没有提示。正确的做法是在错误状态下同时用多种方式传达信息。边框颜色变化之外还需要在输入框的aria-invalid上标注true并且通过aria-describedby指向错误信息的元素。错误信息本身也不是随便一段文字就完了它应该是可被读屏软件聚焦和播报的通常用一个div或者p标签承载并加上rolealert。我见过很多团队会说产品没提无障碍需求先不做但表单验证的无障碍是个例外因为它直接关联用户能否完成核心操作。我会把无障碍作为表单验证的默认强制要求而不是一个可选项。4.5 必填字段的空白值检查空格、零宽字符等藏匿问题用户在一个必填字段里输入一串空格这个值算空还是不算空我的回答是算空。但很多校验库的required规则只检查值是否为或者undefined会让包含空格的字符串直接通过。解决方法是给required规则扩展一个内置逻辑在判断必填时先去除首尾空格如果trim()之后是空字符串则视为未填写。同理对于max长度的校验也应该基于去空格后的值计算否则用户输入 12345 按非空格长度是 5但按原始长度则是 11直接拦住正常的填写。还是那句话细节决定体验。还有一类更隐蔽的问题零宽字符。某些复制粘贴的内容里会夹带\u200b、\u200c这类不可见字符用户看起来填的是 abcde但实际保存下来的是 a\u200bb\u200ccde在后端匹配时对不上。解决方式的思路是在表单提交前做一次统一的数据清洗把所有输入字段做normalize处理把可见字符之外的内容过滤掉。4.6 性能调优高频验证导致输入卡顿有些表单会在 onChange 阶段做全字段全规则的重跑这是一种性能陷阱。用户每点一下键盘所有字段的规则都重新执行一遍特别是包含异步校验时会直接导致输入卡顿、请求爆炸。性能优化的核心是减少校验粒度和执行范围。第一层只在字段自身触发校验时执行该字段的规则不做全量重跑第二层跨字段规则在依赖字段变化时才触发比如确认密码只在密码字段改变时也需要同步校验这通过监听依赖字段实现第三层对于计算开销较大的校验比如正则表达式复杂、需要处理长文本可以启用防抖。另外正则本身写得不好也容易引起性能问题。这里举一个反例(\d)$这种嵌套量词的正则被称为灾难性回溯模式一旦用户输入了一个长字符串且不满足匹配正则引擎会指数级地尝试所有分支浏览器直接卡死。我见过一个真实例子是用户在搜索框里输入了 30 个字符浏览器卡了整整一分钟。排查下来发现校验正则是/^(\w)*$/这就是教科书式的性能炸弹。解决方案是用更智能的写法比如/^\w*$/或者使用正向环视来消除歧义。在这里我建议每个前端开发都把正则的安全性列入审查清单宁可保守写也不要写那种花哨但脆弱的表达式。5. 校验规则的可维护性与测试策略5.1 规则复用与模块化从表单到字段级规则库当项目越来越大你会发现很多校验规则在多个表单里反复出现手机号、邮箱、密码强度、身份证号按掩码处理、公司统一社会信用代码、金额范围、日期范围等等。每出现一次就复制粘贴一份会导致同一规则的实现分散在多处后面如果有人修了一个 bug 但只改了一处就会留下隐患。我的做法是把常用规则收敛为一个独立的规则模块比如src/utils/validation/rules.ts集中导出所有通用规则它们直接是 zod schema 或 async-validator 规则对象这样各表单直接引用即可。同时每个规则旁边配上简要的说明注释和参考的接口文档编号如果它对应后端某个校验。举一个简单的模块化示例// src/utils/validation/rules.ts import { z } from zod; export const phoneRule z .string() .regex(/^1[3-9]\d{9}$/, 请输入有效的手机号); export const emailRule z .string() .email(请输入有效的邮箱地址); export const passwordRule z .string() .min(8, 密码至少8个字符) .regex(/[a-z]/, 需包含小写字母) .regex(/[A-Z]/, 需包含大写字母) .regex(/\d/, 需包含数字) .max(32, 密码最长32个字符); export const cnNameRule z .string() .min(2, 姓名至少2个字符) .max(20, 姓名最多20个字符) .regex(/^[\u4e00-\u9fa5·]$/, 请输入中文姓名可包含中间圆点);表单使用时就是组合这些基础规则再加上自己业务上特有的规则。这样既消除了重复又保持了灵活性。5.2 对校验逻辑进行单元测试不给规则留不透明地带表单验证的规则属于纯函数逻辑是最容易做单元测试的部分但恰恰是最容易被跳过的部分。很多团队觉得校验嘛随便测测就行结果到了生产环境才发现某些规则边界没覆盖到用户数据进不来或者非法数据漏过去都是事故级问题。我用 vitest 或 jest 对规则做参数化测试比如测试手机号规则时把合法值和非法值放进一个表格里逐一断言。这样一旦规则有修改测试会立刻全部暴露行为差异。一套良好覆盖的校验测试相当于给你的表单规则建立了一份可执行的文档。5.3 持续维护校验规则接口变更触发规则同步最后一个建议也是个流程建议表单校验规则要跟着接口定义走。后端接口升级时只要你改了 DTO 的字段约束就必然会带动前端校验规则一起修改。如果前后端没有这个契约意识接口已经允许一种新的格式了前端还在拦着用户不让提交用户体验的伤害是直接的。我手头的做法是后端在修改接口时在变更日志里明确列出校验规则的变化点前端负责表单的同学看到后必须同步更新前端规则并在 MR 描述里注明对应的接口变更文档链接。这个规范看似繁琐但它能避免掉我在实战中遇到的最大的坑规则不一致。规则不一致是表单验证领域里代价最高的 bug因为它通常是静默发生的直到某个用户提交被莫名其妙地拒掉才被发现问题。6. 我对表单验证这件事的体会做了这么多年前端表单验证从来不是最炫酷的那部分工作但绝对是最直接影响业务结果的部分。用户能不能完成注册、能不能顺利下单、能不能正确提交一条信息都压在表单这扇门上。而验证规则的正确性、反馈的及时性、交互的友好性决定了这扇门是轻松推开还是把人挡在外面。从我自己的经验来看做好表单验证的核心不在于用了多厉害的库也不在于手写了一套多有设计感的引擎而在于你用没把用户的体验当成第一优先级。一个不打扰用户、又在需要时精准给出帮助、还能在背后保护系统的验证体系远比一个追求把所有规则一次性验证完且报错信息密集弹窗的方案要难设计。如果这篇文章只留一个观点我会说表单验证的表面是技术实现本质是规则管理。规则清晰、反馈及时、边界可控、前后端一致——把这几件事做扎实了表单验证想不出彩都难。最后分享一个我在实际项目中养成的习惯每次实现完一套表单验证我都会自己把整个流程走一遍模拟五种角色——正常用户、粗心用户填错格式、刁钻用户尝试绕过前端限制、无障碍用户依赖键盘和读屏软件、慢网络用户来回切换卡顿环境。把这五类人走完表单验证的品质基本就有了保障。你也可以试试看。
返回列表