自定义元素需显式启用表单关联(static formAssociated = true)、在constructor中调用attachInternals()、在connectedCallback中同步属性与事件、实现formResetCallback等,否则无法真正参与表单。

自定义元素本身不扩展任何 UI 功能,它只是个可注册的标签壳子;真正扩展控件能力,靠的是你手动封装原生控件、桥接表单逻辑、暴露 internals、同步属性与状态——漏掉任一环,x-input 就只是个会渲染的 div。
为什么 customElements.define() 后控件仍不参与表单
最常见原因是没启用表单关联机制。浏览器不会因为你写了 <x-input></x-input> 就自动把它当 <input> 处理。
- 必须在类上声明
static formAssociated = true,否则this.attachInternals()会直接抛错 - 必须在
constructor中调用this.attachInternals(),不能拖到connectedCallback里 - 必须在
connectedCallback中监听子<input>的input或change事件,并显式调用this.internals.setFormValue() - 若要支持
form.reset(),还得实现formResetCallback(),否则值不会被清空
attributeChangedCallback 为什么收不到初始属性值
HTML 中写的 <x-toggle checked></x-toggle> 确实会触发回调,但 oldValue 是 null,且该回调只响应 setAttribute 类变更,对 JS 属性赋值(如 el.checked = true)完全无感。
- 必须在
static get observedAttributes()中显式列出要监听的属性,比如['checked', 'disabled'] - 初始值同步得自己补:在
connectedCallback里读this.hasAttribute('checked')或this.getAttribute('checked'),再设内部<input>状态 - 布尔属性(如
checked)的newValue是字符串""或"true",不是布尔值,别直接=== true - 想响应
el.value = 'xxx'这类操作?得重写valuesetter,并在里面调this.internals.setFormValue()
Shadow DOM 里样式失效、焦点进不去、键盘不响应
Shadow DOM 隔离了样式和事件流,也切断了原生交互链路。加个 role="button" 不等于有了按钮行为。
立即学习“前端免费学习笔记(深入)”;
- 内部必须放一个真实
<button>或<input type="checkbox">,而不是仅用<div tabindex="0">模拟 - 键盘激活(空格/Enter)得手动监听
keydown,并调用内部控件的click()或toggleAttribute() - 禁用态(
disabled)必须双向同步:外部设el.disabled = true→ 内部input.disabled = true;内部变化也要反推回宿主属性 - 样式要注入 Shadow Root:
const style = document.createElement('style'); style.textContent = 'button { background: blue; }'; this.shadowRoot.appendChild(style);
扩展内置元素(如 <button is="x-button">)为什么属性不生效
扩展现有标签不是“换皮”,而是“继承+接管”。漏掉 is 属性或继承错误基类,所有原生能力都会消失。
- 必须继承对应内置类,比如扩展
<button>得写class XButton extends HTMLButtonElement,不能继承HTMLElement - 注册时必须传
{ extends: 'button' },且使用时只能写<button is="x-button">—— 写成<x-button>就是普通未知元素 -
is值必须和customElements.define()第一个参数完全一致,大小写、连字符都不能错 - 原生属性(
type、form、autofocus)仍可用,但需在attributeChangedCallback中手动同步到内部逻辑
真正难的不是写个能渲染的 x-slider,而是让它在 form.elements 里出现、能被 form.checkValidity() 校验、按 Tab 键能自然进入、屏幕阅读器能准确播报当前值——这些不是可选项,是语义化控件的硬门槛。



















