如何通过HTML自定义元素高效创建统一规范的Web UI组件?
- 内容介绍
- 文章标签
- 相关推荐
本文共计836个文字,预计阅读时间需要4分钟。
直接输出结论:
customElements.define 的参数陷阱
注册时第二个参数必须是继承 HTMLElement 的类,不能是普通函数或箭头函数。常见错误是直接传入对象字面量或忘记 extends HTMLElement:
// ❌ 错误:不是类,也不是 HTMLElement 子类 customElements.define('x-input', { connectedCallback() {} }); // ✅ 正确:必须是 class,且显式继承 class XInput extends HTMLElement { connectedCallback() { this.innerHTML = '<input type="text">'; } } customElements.define('x-input', XInput);
- 类名必须含短横线(
-),比如data-table合法,datatable会抛DOMException: The element name must contain a hyphen - 同一个名字只能注册一次,重复调用
customElements.define会报错,开发时建议加守卫:if (!customElements.get('x-input')) { customElements.define(...); } - 类不能带构造函数里调用
super(),否则实例化失败
attributeChangedCallback 怎么监听才可靠
这个回调只响应通过 setAttribute、removeAttribute 或 HTML 属性初始值触发的变更,对 el.value = 'xxx' 这类 JS 属性赋值完全无感。想做到“属性 ↔ 状态”同步,得自己补逻辑:
class XToggle extends HTMLElement { static get observedAttributes() { return ['disabled', 'checked']; } attributeChangedCallback(name, oldValue, newValue) { // 注意:newValue 是字符串,布尔属性如 checked 可能是 null 或 'true' if (name === 'checked') { const input = this.shadowRoot.querySelector('input'); input.checked = newValue !== null; } } }
- 必须在
static get observedAttributes()里显式声明要监听的属性,漏写就收不到回调 - 初始 HTML 中写的
<x-toggle checked></x-toggle>会触发一次回调,但oldValue是null,别直接拿它做相等判断 - 不要在回调里改自身属性(比如又调
this.setAttribute('checked', ...)),否则会无限循环
Shadow DOM 不是万能隔离,style 和 slot 有边界
用 this.attachShadow({ mode: 'closed' }) 能防止外部 JS 访问内部结构,但样式穿透和 slot 内容仍受限制:
立即学习“前端免费学习笔记(深入)”;
-
<style>写在 shadowRoot 里才生效,全局 CSS 不会进入 shadow tree,但:host和::slotted是特例 -
<slot>默认是匿名插槽,如果组件允许用户传入任意内容(比如按钮文字),必须用具名 slot 并加name属性,否则<x-button><span slot="icon">?</span>Save</x-button>会失效 -
mode: 'closed'下,this.shadowRoot为null,调试困难;日常开发建议先用'open',上线前再切
真正难的不是写一个能跑的自定义元素,而是让团队所有人遵守同一套属性命名(比如统一用 size 不用 scale)、事件命名(比如都发 x-change 而不是 onSelect)、以及是否允许用户覆盖内部样式——这些约定不会被浏览器校验,全靠文档和 Code Review 卡住。没这层共识,控件越写越多,反而成了技术债加速器。
本文共计836个文字,预计阅读时间需要4分钟。
直接输出结论:
customElements.define 的参数陷阱
注册时第二个参数必须是继承 HTMLElement 的类,不能是普通函数或箭头函数。常见错误是直接传入对象字面量或忘记 extends HTMLElement:
// ❌ 错误:不是类,也不是 HTMLElement 子类 customElements.define('x-input', { connectedCallback() {} }); // ✅ 正确:必须是 class,且显式继承 class XInput extends HTMLElement { connectedCallback() { this.innerHTML = '<input type="text">'; } } customElements.define('x-input', XInput);
- 类名必须含短横线(
-),比如data-table合法,datatable会抛DOMException: The element name must contain a hyphen - 同一个名字只能注册一次,重复调用
customElements.define会报错,开发时建议加守卫:if (!customElements.get('x-input')) { customElements.define(...); } - 类不能带构造函数里调用
super(),否则实例化失败
attributeChangedCallback 怎么监听才可靠
这个回调只响应通过 setAttribute、removeAttribute 或 HTML 属性初始值触发的变更,对 el.value = 'xxx' 这类 JS 属性赋值完全无感。想做到“属性 ↔ 状态”同步,得自己补逻辑:
class XToggle extends HTMLElement { static get observedAttributes() { return ['disabled', 'checked']; } attributeChangedCallback(name, oldValue, newValue) { // 注意:newValue 是字符串,布尔属性如 checked 可能是 null 或 'true' if (name === 'checked') { const input = this.shadowRoot.querySelector('input'); input.checked = newValue !== null; } } }
- 必须在
static get observedAttributes()里显式声明要监听的属性,漏写就收不到回调 - 初始 HTML 中写的
<x-toggle checked></x-toggle>会触发一次回调,但oldValue是null,别直接拿它做相等判断 - 不要在回调里改自身属性(比如又调
this.setAttribute('checked', ...)),否则会无限循环
Shadow DOM 不是万能隔离,style 和 slot 有边界
用 this.attachShadow({ mode: 'closed' }) 能防止外部 JS 访问内部结构,但样式穿透和 slot 内容仍受限制:
立即学习“前端免费学习笔记(深入)”;
-
<style>写在 shadowRoot 里才生效,全局 CSS 不会进入 shadow tree,但:host和::slotted是特例 -
<slot>默认是匿名插槽,如果组件允许用户传入任意内容(比如按钮文字),必须用具名 slot 并加name属性,否则<x-button><span slot="icon">?</span>Save</x-button>会失效 -
mode: 'closed'下,this.shadowRoot为null,调试困难;日常开发建议先用'open',上线前再切
真正难的不是写一个能跑的自定义元素,而是让团队所有人遵守同一套属性命名(比如统一用 size 不用 scale)、事件命名(比如都发 x-change 而不是 onSelect)、以及是否允许用户覆盖内部样式——这些约定不会被浏览器校验,全靠文档和 Code Review 卡住。没这层共识,控件越写越多,反而成了技术债加速器。

