模块化重构html表单校验需分离「触发、执行、反馈」三层职责,用语义标签(、、)界定模块边界,校验逻辑分即时层、预检层、业务层三级调度,错误提示状态驱动、统一收口,异步态用aria-busy标记,隐藏字段须移除required或设hidden属性。

模块化重构HTML表单校验,不是把校验逻辑拆成多个JS文件就完事——关键在于把「谁触发」「谁执行」「谁反馈」三层职责彻底分离,否则改一个提示文案就得翻遍form.js、validator.js和ui.js。
表单结构必须用语义标签锚定模块边界
别用div class="form-section"包裹整块校验区域,浏览器无法识别它是否该被读屏器跳过,也无法在无障碍树中建立独立上下文。真正能承载模块边界的只有原生语义标签。
-
<form></form>是顶层容器,所有校验逻辑的调度起点,不能嵌套在div里 - 每个字段组(如“密码+确认密码”)必须包在
<fieldset></fieldset>里,配合<legend></legend>提供可访问的分组说明 - 错误提示区统一用
<div role="alert" aria-live="polite">,不依赖class名定位,避免CSS重置后UI与AR脱节 <li>禁止在<code>input外层再套一层div来控制样式,会导致:valid/:invalid伪类失效 - 即时层(input/blur):只做
required、type="email"、minlength等原生约束 + 轻量正则(如密码强度),用setCustomValidity('')清空后立即生效 - 预检层(submit前):调
form.checkValidity(),失败则直接reportValidity(),不执行任何fetch - 业务层(submit且checkValidity()通过后):启动责任链,每个
CheckHandler只负责一件事,如UsernameUniquenessCheckHandler只发一次fetch("/api/user/check?username=xxx"),返回false即中断链并阻止提交 - 所有错误消息统一收口到
ValidationMessages对象,键名为字段name + 错误类型,例如{ "email": { "uniqueness": "该邮箱已被注册" } } - UI层只监听
form.reportValidity()失败或责任链onError事件,根据当前字段name和错误码查表渲染,不拼接字符串 - 异步校验中的loading态,用
aria-busy="true"标记输入框,而非加disabled——否则屏幕阅读器会跳过它,用户无法感知等待中 - 错误清除不靠
innerHTML = '',而是统一触发clearValidity(),让:invalid伪类自动消失
校验逻辑按触发时机分三层,不能混写在submit事件里
常见错误是把同步正则校验、异步唯一性检查、跨字段比对全塞进form.addEventListener('submit', ...),结果用户点提交后卡顿、提示错乱、连点多次触发多请求。
自定义错误提示必须脱离DOM操作,靠状态驱动
硬编码document.getElementById('email-error').textContent = '邮箱已被注册'会让后续加国际化、灰度开关、A/B测试变得极其脆弱。
最易被忽略的是:模块化后,form.checkValidity()仍会遍历所有input,包括被display: none隐藏但未remove的字段。这些字段若带required,就会导致校验永远失败——得用input.hidden = true或移除required属性,而不是靠CSS藏起来。











