原生HTML组件化依赖CustomElementRegistry和Shadow DOM,需继承HTMLElement并注册;constructor中初始化Shadow DOM,用observedAttributes和attributeChangedCallback处理属性变化,CSS通过自定义属性定制,生产环境需polyfill。

原生 HTML 的组件化不靠框架,靠 CustomElementRegistry 和 Shadow DOM —— 但直接上手容易卡在生命周期、样式隔离或属性同步上。
怎么定义一个可复用的自定义元素
必须继承 HTMLElement,并用 customElements.define() 注册。不能只写 class,也不能漏掉 extends HTMLElement,否则浏览器会报 Failed to construct 'customElements': The result must be an instance of HTMLElement。
常见写法:
class MyButton extends HTMLElement {
constructor() {
super();
const shadow = this.attachShadow({ mode: 'open' });
shadow.innerHTML = `<button><slot></slot></button>`;
}
}
customElements.define('my-button', MyButton);
-
constructor是唯一能调用super()和初始化 Shadow DOM 的地方 -
mode: 'open'允许 JS 通过element.shadowRoot访问;用'closed'会屏蔽外部操作,调试困难 -
<slot></slot>是内容分发入口,没它就无法传入子节点
怎么让组件响应属性变化
静态属性(如 disabled、size)需要显式声明 observedAttributes,再在 attributeChangedCallback 里处理。HTML 属性不会自动映射到 JS 属性,也不会触发重渲染。
例如支持 size 属性:
static get observedAttributes() {
return ['size'];
}
attributeChangedCallback(name, oldValue, newValue) {
if (name === 'size') {
this.shadowRoot.querySelector('button').className = `btn-${newValue}`;
}
}
- 只监听列表里的属性,拼写错一个(比如写成
'Size')就完全不触发 -
oldValue和newValue都是字符串,即使你传<my-button size="2"></my-button>,拿到的也是"2" - 首次渲染时,属性已存在,但
attributeChangedCallback不会执行 —— 需在connectedCallback中补一次同步
怎么避免 Shadow DOM 样式泄漏和穿透
Shadow DOM 默认隔离样式,但容易误以为外部 CSS 能控制内部元素,或反过来想用全局样式覆盖组件内样式。
- 组件内样式必须写在
shadow.innerHTML里,或用<style></style>标签注入,不能依赖外部<link rel="stylesheet"> - 想让外部能定制颜色?用 CSS 自定义属性:
button { background: var(--btn-bg, #007bff); },然后用户写<my-button style="--btn-bg: red"></my-button> - 想穿透进 Shadow DOM 写样式?不行。但可以用
:host、:host-context()或::slotted()控制宿主和插槽内容
Web Components 在真实项目里要注意什么
不是所有浏览器都默认支持全部特性:Safari 对 constructable stylesheets 支持滞后,IE 完全不支持,HTMLTemplateElement 在旧版 Android WebView 有 bug。
- 生产环境务必加 polyfill:
@webcomponents/webcomponentsjs,且要区分webcomponents-loader.js(动态加载)和webcomponents-bundle.js(全量) - 不要在
disconnectedCallback里清理全局事件监听器以外的逻辑 —— 它可能被频繁触发(比如 vdom diff 时移入移出) - 和 React/Vue 混用时,属性传递要用
camelCase写法(如myProp),但 HTML 中必须写成my-prop,大小写不匹配会导致属性丢失
真正麻烦的从来不是定义组件,而是属性同步时机、CSS 作用域边界、以及跨框架通信时的事件命名冲突 —— 这些细节不提前踩一遍,上线后 debug 成本远高于初期多写几行防御逻辑。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











