自定义元素需封装原生input并代理验证API才能触发原生校验;必须启用formAssociated、attachInternals、属性透传、方法代理及事件监听,否则无法参与表单验证流程。

自定义元素无法直接触发原生验证,必须封装原生 input
浏览器只对 <input>、<select>、<textarea> 这类原生控件执行约束验证(Constraint Validation API)。写 <my-input required></my-input> 时,required 只是普通 HTML 属性,不会激活任何校验逻辑——my-input 不在可约束元素列表里。
真正能走通的路径,是让自定义元素内部创建并托管一个原生 input,再把所有验证相关行为桥接到它身上。否则 reportValidity() 会直接报错 “not a function”,checkValidity() 也不存在。
- 必须用
attachShadow创建 Shadow DOM,并在其中放置真实<input> -
constructor里要立即拿到这个内部input实例(如this.input = this.shadowRoot.querySelector('input')) - 不能只靠
connectedCallback同步属性——首次渲染前就要准备好验证代理基础
属性透传和方法代理是验证生效的前提
即使内部有 input,required、pattern、min、max 等属性也不会自动同步过去。你得手动监听 attribute 变化,并显式赋值给内部 input:
- 用
static get observedAttributes()声明要监听的属性名(如['required', 'pattern', 'min', 'max', 'type']) - 在
attributeChangedCallback中,根据name把新值设到this.input[name] = value或this.input.setAttribute(name, value) -
reportValidity()必须重写为return this.input.reportValidity();同理checkValidity()返回this.input.checkValidity() -
validity、validationMessage、willValidate都要代理读取this.input对应属性
formAssociated 是让自定义元素“被表单识别”的关键
没有 formAssociated: true,你的 <my-input> 就算封装得再完整,也不会出现在 form.elements 里,form.reportValidity() 也完全无视它。
立即学习“前端免费学习笔记(深入)”;
启用该选项后,还需实现 formAssociatedCallback 和 get form() getter,否则表单仍无法关联到内部 input 的验证状态。
- 必须在
constructor中调用this.internals_ = this.attachInternals()(注意下划线命名是规范要求) -
formAssociatedCallback(form)要保存form引用,并在内部input上监听invalid事件,转发给外部 -
get form()必须返回this.internals_.form,否则表单提交时无法收集它的有效性 - 不实现这些,
<form>提交时根本不会触发你的reportValidity(),也不参与整体校验流程
实时校验容易漏掉状态清理和事件绑定时机
原生验证默认只在 submit 时触发,但用户期望输入就反馈。这时候常犯的错不是“没加监听”,而是监听了却没清状态或绑错节点。
- 在
input或blur事件里调this.input.reportValidity()前,必须先this.input.setCustomValidity(''),否则旧错误残留 - 不要监听自定义元素自身(如
this.addEventListener('input', ...)),而要监听内部this.input—— 它才是真实触发事件的节点 - 如果用了
novalidate关闭原生提交校验,又忘了在submit事件里手动调form.checkValidity(),整个验证链就断了 - 移动端 WebView(尤其低端安卓)可能不支持
reportValidity()气泡,需 fallback 到 class 切换 + 自定义提示层
最易被忽略的是:哪怕你封装得滴水不漏,type="number" 依然允许粘贴字母,value 变为空字符串但 checkValidity() 返回 true——这种边界必须额外用 input.value === '' || isNaN(input.value) 补判。



















