event.preventDefault() 必须是submit事件处理器第一行,否则验证失败会导致页面刷新、错误提示不显示;setCustomValidity需配合checkValidity和preventDefault才生效;后端错误字段名须与input[name]严格一致,并清空旧提示后再渲染。

submit 事件里漏了 event.preventDefault() 就别谈验证失败处理
表单验证失败后页面刷新、错误提示不显示、请求根本没发出去——八成是这一行漏了。它不是锦上添花,而是异步提交和手动接管验证的硬性前提。
常见错误包括:
– 把 preventDefault() 写在验证逻辑后面,结果验证失败时根本没执行到
– 在按钮 onclick 里发请求,却没监听 form 的 submit 事件
– 用内联 onsubmit="handle()" 却没在函数末尾 return false
正确姿势只有一条:event.preventDefault() 必须是事件处理器第一行,雷打不动
setCustomValidity() 调用后没反应?检查这三件事
这个 API 看似简单,但实际生效依赖隐含状态:它只改当前元素的 validity 状态,不触发 UI 反馈,也不阻止提交——除非你在 submit 里显式调用 form.checkValidity() 并配合 preventDefault()。
立即学习“前端免费学习笔记(深入)”;
-
setCustomValidity("错误信息")才表示失败;setCustomValidity("")才代表“通过” - 调用后必须再触发一次验证(比如
input.blur()或form.submit()),否则:invalid伪类不会更新 - 多个字段校验时,别只检查第一个——要用
Array.from(form.elements).every(el => el.checkValidity())
后端返回 400 错误,怎么把字段级提示塞进对应 input
最常卡住的地方不是“怎么显示”,而是“映射错位”和“旧提示残留”。后端返回的字段名(如 {"user_email": ["邮箱已被注册"]})和前端 input[name] 不一致,JS 就找不到目标节点。
实操要点:
– 服务端字段名必须和 input[name] 完全一致(区分大小写、下划线/驼峰)
– 每个 input 后紧跟一个 <span class="error-message"></span>,不要用全局 toast 替代字段级定位
– 渲染新错误前,先清空所有旧提示:form.querySelectorAll(".error-message").forEach(el => el.remove())
– 校验失败后,记得 input.focus(),否则用户可能不知道哪错了
移动端 Safari 里 checkValidity() 不触发?换时机
这个方法在桌面浏览器里通常没问题,但在 iOS Safari 上对触发时机敏感:等 submit 时才查,往往太晚——用户已经点了两次提交,UI 还没更新。
建议提前介入:
– 在 input 或 blur 事件中主动调用 input.checkValidity(),并同步更新 aria-invalid 和提示文案
– 给出错字段加 aria-invalid="true" 和 aria-describedby="xxx-error",读屏器才可感知
– 避免只靠 CSS 类控制显隐,禁用样式的用户会看不到任何反馈
setCustomValidity、每次渲染后端错误、每次焦点切换,都得同步清理、聚焦、标记、移除——少一步,体验就断一截。



















