HTML自定义元素驱动的插件式网页开发模式
自定义元素命名必须含短横线且全小写,避免与原生HTML元素冲突。构造函数仅执行super()和附加影子DOM,DOM操作必须移至connectedCallback中。样式需注入shadowRoot,外部CSS无效,以保障样式封装性。注册组件必须等待DOM完全就绪,否则组件静默失效无法使用。
在实际开发中,自定义元素是构建可复用组件的重要基础,但许多开发者容易在细节上踩坑。以下核心要点,几乎都是绕不开的硬性规则。
### 自定义元素命名必须含短横线且全小写
如果直接使用`mybutton`或`MyButton`,浏览器会直接拒绝——抛出`DOMException: The name "xxx" is not a valid custom element name`。只有像`date-picker`、`status-badge`、`nav-menu`这样带短横线且全小写的名称,才能通过注册。
这不是风格建议,而是硬性校验:调用`customElements.define()`的瞬间就会失败,组件根本进不了初始化流程。别指望用`try/catch`兜底,错误发生在注册阶段,后续逻辑根本没机会执行。
禁止使用下划线(`my_button`)、大写字母(`UserCard`)、单个单词(`button`),也不能与原生标签重名(比如`div`、`slot`、`template`这些)。另外,`html-`、`svg-`、`math-`这类保留前缀也要避开。
### constructor里只能调super()和attachShadow,DOM操作必须移进connectedCallback
很多人在`constructor`里写`this.innerHTML = ''`或`this.getAttribute('value')`,结果发现属性读不到、样式不生效、`this.shadowRoot`是`null`。问题出在`constructor`执行时元素还没挂载到文档,DOM 上下文根本不可用。
正确的分工是这样的:
- `constructor`只做三件事:调`super()`、调`this.attachShadow({ mode: 'open' })`(如果需要 Shadow DOM)、初始化私有字段或绑定方法
- 所有 DOM 查询(`this.shadowRoot.querySelector`)、属性读取(`this.getAttribute('label')`)、事件监听(`this.shadowRoot.addEventListener`)、模板渲染,都必须放在`connectedCallback`里
- `attributeChangedCallback`不会触发初始属性值,首次渲染还得在`connectedCallback`里手动读取
- 如果组件可能被多次挂载/卸载(比如路由切换),`disconnectedCallback`要清理定时器和事件监听器,否则会内存泄漏
### 样式必须注入shadowRoot,不能靠外部CSS或@import
把``写在自定义元素内部,或者用`@import`加载外部样式,在 Shadow DOM 里基本无效。Shadow DOM 的样式隔离机制决定了:样式必须显式注入`this.shadowRoot`。
最稳妥的做法是在`connectedCallback`中拼接``字符串并插入`shadowRoot`。例如:
```ja vascript
const style = document.createElement('style');
style.textContent = `:host { display: inline-block; } .input { border: 1px solid #ccc; }`;
this.shadowRoot.appendChild(style);
```
如果样式复杂,可以预编译为字符串常量,避免运行时重复解析。想让外部控制尺寸或主题色,要用 CSS 自定义属性配合`:host`选择器,比如`:host([size="large"]) { font-size: 1.2em; }`。
### customElements.define必须等DOM就绪后执行
脚本放在``里同步执行,而``标签在``里,浏览器解析到这个标签时`customElements.define()`还没跑,就会当普通未知元素处理——哪怕后面注册成功,也不会自动升级,`connectedCallback`永远不触发。
✅ 正确做法有两种:
- 用`document.addEventListener('DOMContentLoaded', () => { customElements.define('my-card', MyCard); });`
- 用``放在``顶部。动态插入的`

