原生校验与后端错误码是分层协作关系:前者用required、type等属性在提交前拦截格式错误,后者根据返回code、field等字段执行字段/按钮/全表单锁定。

原生校验和后端错误码不是并列关系,而是分层协作:原生校验管“格式对不对”,后端错误码管“业务能不能做”。所谓“双重锁定”,其实是前端在提交前用原生规则拦住明显错误,在提交后根据后端返回的错误码决定是否禁用操作、展示提示、或临时锁住字段。
先靠原生校验快速拦截低级错误
用 required、type="email"、pattern、minlength 等属性在用户输入或失焦时就给出即时反馈。这些不依赖网络,响应快,能挡住 80% 的无效提交。
- 比如邮箱输成 “abc@” ,浏览器自动标红并提示“请填写有效的电子邮件地址”,根本不会发请求
- 密码长度不够、手机号位数不对、日期超出范围等,都在 submit 前就被
checkValidity()拦下 - 此时按钮不用手动禁用——只要没通过校验,
form.reportValidity()就会中断流程,用户点不动
提交后根据后端错误码做业务层锁定
原生校验通过后才发请求。服务端返回错误码(如 {"code": 409, "msg": "用户名已存在"} 或 {"locked": true, "retry_after": 300}),前端据此执行对应动作:
-
字段级锁定:对报错字段调
input.setCustomValidity("用户名已被注册")+input.reportValidity(),让它和原生错误一样显示 -
按钮级锁定:若返回
"locked": true,立即设button.disabled = true,并配合倒计时或提示“5分钟后可重试” - 全表单锁定:如风控触发(连续失败3次),可给整个 form 加 class="locked",禁用所有 input,并隐藏提交按钮
-
状态持久化:把锁定状态存进
localStorage,刷新页面后仍保持禁用,避免用户误操作
关键细节:别让两层校验互相干扰
原生校验是“静态规则”,后端错误是“动态结果”,二者要隔离触发时机:
- 不要在
input输入时就发查重请求——避免无效请求和状态混乱;只在submit且checkValidity() === true后再发起 - 每次发请求前,必须先清空自定义错误:
input.setCustomValidity(""),否则旧提示残留,新错误不显示 - 后端返回错误后,不能只改 UI,还要同步更新 DOM 状态:比如把
input设为aria-invalid="true",确保读屏软件可识别 - 如果后端返回的是全局锁定(如账号冻结),前端应跳转到专用提示页,而不是留在表单页让用户反复尝试
服务端需配合提供结构化错误信息
前端能否精准锁定,取决于后端返回的内容是否明确:
- 推荐返回带
field字段的错误对象,例如:{"field": "username", "code": "already_exists", "message": "该用户名已被占用"} - 锁定类错误建议附带
retry_after(秒数)或unlock_time(ISO 时间),方便前端计算倒计时 - 避免只返回模糊的
{"error": "操作失败"}——前端无法知道锁哪、锁多久、怎么提示









