表单校验实战:HTML5 约束验证 API 与自定义校验对比

CLARA轻量论坛系统
CLARA轻量论坛系统 星耀SVIP管理员 黑卡会员
发布于 2026-09-20 03:45 ·3 浏览 ·0 回复

学完这篇,你能在一小时内搞清楚「什么时候直接用 HTML5 约束验证、什么时候必须自己写校验」,并拿到一份可复制的混合方案代码。

表单校验的坑不在"能不能校验",而在"提示文案能不能改、异步规则能不能塞、跨字段依赖怎么处理"。下面按从简到繁的顺序走一遍。

第一步:先用 HTML5 原生属性把校验声明出来

不用写一行 JS,浏览器自带校验就能覆盖大半场景:

<form id="signup" action="/api/signup" method="post">
  <label>用户名
    <input name="username" required minlength="3" maxlength="16"
           pattern="[a-zA-Z0-9_]+" autocomplete="username">
  </label>
  <label>邮箱
    <input name="email" type="email" required>
  </label>
  <label>年龄
    <input name="age" type="number" min="18" max="120">
  </label>
  <button>注册</button>
</form>

这些属性会直接映射到 `ValidityState`:`required` → `valueMissing`,`type="email"` → `typeMismatch`,`pattern` → `patternMismatch`,`min/max` → `rangeUnderflow/rangeOverflow`。

注意:`pattern` 是整个值全匹配,浏览器自动补 `^...$`。所以写 `pattern="\d{6}"` 而不是 `pattern="^\d{6}$"`,否则部分浏览器行为不一致。另外 `pattern` 对中文用户不友好——你写 `[a-zA-Z0-9_]+`,用户粘一个全角符号进来只能看到"请匹配要求的格式",看不懂。

第二步:认识约束验证 API 的常用成员

成员作用
`el.validity`只读对象,`valid/valueMissing/typeMismatch/customError` 等布尔位
`el.validationMessage`当前浏览器生成(或你设置的)提示文案
`el.checkValidity()`返回布尔,并触发 `invalid` 事件
`el.reportValidity()`在 `checkValidity` 基础上,真正把气泡弹给用户看
`el.setCustomValidity(msg)`写入自定义错误,`msg` 为空串表示清除
`form.checkValidity()`一次性校验所有可校验控件
`form.noValidate`置 true 后浏览器不再自动拦截提交
const form = document.getElementById('signup');
form.addEventListener('submit', (e) => {
  if (!form.checkValidity()) {
    e.preventDefault();
    form.reportValidity(); // 弹出第一个不合格字段的气泡
  }
});

第三步:用 setCustomValidity 补规则

这是 HTML5 路线里最实用的一招——保留浏览器气泡 UI,只替换规则:

const name = form.elements.username;

name.addEventListener('input', () => name.setCustomValidity('')); // 必须清
name.addEventListener('blur', () => {
  if (name.validity.valueMissing) return;      // 空值交给内置文案
  if (/^\d/.test(name.value)) {
    name.setCustomValidity('用户名不能以数字开头');
  }
});

注意:`setCustomValidity` 只要设了非空串,`validity.customError` 就一直是 `true`,这个字段永远无法通过校验。所以每次输入都要无条件清空一次。这是新手最常见的"表单死活提交不了"原因。

第四步:完全接管——novalidate + 自写规则

一旦出现异步校验(用户名查重)或跨字段校验(确认密码、结束时间大于开始时间),HTML5 就无能为力了,只能自己来:

<form id="signup" novalidate>
const rules = {
  username: [
    v => v.trim() !== '' || '用户名不能为空',
    v => v.length >= 3 || '至少 3 个字符',
    v => /^[a-zA-Z0-9_]+$/.test(v) || '只能包含字母、数字、下划线',
    async v => (await isNameTaken(v)) ? '该用户名已被占用' : '',
  ],
  password2: [
    (v, data) => v === data.password || '两次输入的密码不一致',
  ],
};

async function validateField(input) {
  const list = rules[input.name] || [];
  for (const rule of list) {
    const msg = await rule(input.value, Object.fromEntries(new FormData(form)));
    if (msg) { showError(input, msg); return false; }
  }
  clearError(input);
  return true;
}

然后监听 `blur`(失焦即校验)和 `submit`(全部走一遍,用 `Promise.all` 并发跑异步规则)。

注意:自写校验必须补无障碍部分——给错误提示容器加 `aria-live="polite"`,给 input 加 `aria-invalid="true"` 和 `aria-describedby`。浏览器原生气泡是自带读屏支持的,你自己画的 UI 不会自动获得。

第五步:两者怎么选

维度HTML5 约束验证自定义校验
代码量零 JS规则 + 渲染 + 状态管理
提示样式浏览器气泡,改不了完全可控
异步规则不支持支持
跨字段依赖不支持支持
一致性各浏览器文案/时机不同完全统一

结论是别二选一,做两层:用 `type/required/min/max/pattern` 做声明式第一层,同时给表单加 `novalidate` 关掉默认气泡,然后自己接管 `invalid` 事件:

form.addEventListener('invalid', (e) => {
  e.preventDefault();          // 阻止浏览器气泡
  showError(e.target, e.target.validationMessage);
}, true);                      // 必须用捕获阶段

注意:`invalid` 事件不冒泡,在 form 上用第三个参数 `true` 走捕获阶段才能收到。少了这个 `true`,你会以为它压根没触发。

再配 CSS 伪类收尾:`input:user-invalid { border-color: #e5484d; }`。用 `:user-invalid` 而不是 `:invalid`——`:invalid` 在页面刚加载时就会命中所有未填的必填项,红框一片,体验很差。

小结

  • HTML5 约束验证零代码、无障碍免费,适合必填、格式、范围这类同步单字段规则。
  • `pattern` 是全匹配、`setCustomValidity` 必须手动清、`invalid` 事件必须捕获阶段监听,这三个是翻车高发点。
  • 异步校验(查重)和跨字段校验(确认密码)只能自定义,没有捷径。
  • 生产环境的最优解是混合:属性声明规则 + `novalidate` 关气泡 + 捕获 `invalid` 事件接管提示 + 自写异步规则。
  • 样式用 `:user-invalid`,别用 `:invalid`;自绘错误 UI 记得补 `aria-live` 和 `aria-describedby`。
本文转载自 Clara轻量论坛系统,原文地址:https://www.leleweb.cn/thread-538.html
转载请注明出处,版权归原作者所有。

全部回复 0

还没有回复,来抢沙发~