Web Component 必须通过 JavaScript 用 customElements.define() 注册后才能生效,直接写 HTML 标签无效;核心是继承 HTMLElement 的 class,需 super()、支持 Shadow DOM 和 attributeChangedCallback 响应属性变化。

Web Component 不是“用 HTML 做出来”的,而是用 JavaScript 定义、用 HTML 标签调用的——HTML 本身只负责声明式使用,核心逻辑和封装必须靠 customElements.define() 和 class 实现。
为什么直接写 HTML 标签无法创建 Web Component
浏览器不会把任意自定义标签(比如 <my-button>)自动识别为组件。它需要先被 JS 注册,否则只是个未知元素,不响应事件、无样式作用域、无生命周期钩子。
- 未注册时,
document.querySelector('my-button')能取到节点,但instanceof HTMLElement为true,却不是你期望的类实例 - Shadow DOM 不会自动挂载;CSS 不会自动隔离;
connectedCallback根本不会触发 - 常见错误现象:
Uncaught DOMException: Failed to execute 'define' on 'CustomElementRegistry': the name "my-button" has already been used—— 多次注册同名导致,但更常见的是压根没注册就写了标签
最简可用的 Web Component 写法(含 Shadow DOM)
绕不开 class 继承 HTMLElement,且必须调用 super()。Shadow DOM 是可选但推荐的封装手段。
class MyButton extends HTMLElement {
constructor() {
super();
const shadow = this.attachShadow({ mode: 'open' });
shadow.innerHTML = `
<style>button { background: #007bff; color: white; border: none; }</style>
<button><slot></slot></button>
`;
}
}
customElements.define('my-button', MyButton);
-
mode: 'open'允许外部 JS 通过el.shadowRoot访问,'closed'则禁止——调试时别用closed,否则查不到结构 -
<slot>是内容分发点,不写就看不到传入的文本或子元素;多个 slot 需加name属性匹配 - 不能在
constructor里读取this.innerHTML或调用this.getAttribute()—— 此时属性还没解析完,要用attributeChangedCallback
如何响应属性变化并更新 UI
静态渲染不够用,得监听属性变更。Web Component 规范要求显式声明要观察的属性,否则 attributeChangedCallback 不会触发。
使用 PptxGenJS 和 Python 解析,将结构化的 HTML/CSS 幻灯片内容转换为原生、可编辑的 PowerPoint(.pptx)演示文稿。
立即学习“前端免费学习笔记(深入)”;
class MyButton extends HTMLElement {
static get observedAttributes() {
return ['disabled', 'label'];
}
attributeChangedCallback(name, oldValue, newValue) {
if (name === 'disabled') {
this.shadowRoot.querySelector('button').disabled = newValue !== null;
}
}
}
- 只监听列表里的属性:比如写了
observedAttributes返回['disabled'],那改label属性不会进这个回调 - 值是字符串:即使你写
<my-button disabled>,newValue也是空字符串'',不是true;判断是否设置用newValue !== null - 首次初始化属性(如
<my-button disabled>)也会触发该回调,所以适合做初始状态同步
兼容性与生产注意事项
Chrome/Firefox/Edge 新版本原生支持没问题,但 Safari 对 adoptedCallback 支持不全,且所有浏览器都不支持在 <template> 中直接用自定义标签(需 JS 动态插入)。
- 不要依赖
innerHTML设置 Shadow DOM 内容——IE 不支持,且易受 XSS 影响;用document.createElement+appendChild更安全 - 避免在
connectedCallback里重复初始化:组件可能多次连接/断开(如 Vue/React 中 v-if 切换),应加 flag 控制 - 若需兼容旧浏览器,必须引入
@webcomponents/webcomponentsjspolyfill,但注意它不支持shadowRoot.mode = 'closed'
真正卡住人的往往不是语法,而是生命周期时机错位:比如在 constructor 里访问 this.children 总是空的,因为此时 HTML 解析还没完成——这种细节不踩一遍坑很难记住。


















