自定义元素是构建可插拔UI组件的轻量标准载体,需合规命名、在connectedCallback中操作DOM和样式、用attributeChangedCallback安全通信。

自定义元素本身不是插件系统,但它是构建可插拔 UI 组件的最轻量、标准兼容的载体——关键在于把 customElements.define() 当注册入口,把 connectedCallback 当启动钩子,把 attributeChangedCallback 当通信通道。
自定义元素命名必须带连字符且全小写
浏览器在调用 customElements.define() 时会立刻校验名称。不合规就直接抛 DOMException: The name "xxx" is not a valid custom element name,不会进入后续逻辑。
-
date-picker✅ 合规(含连字符、全小写) -
mybutton❌ 单词无连字符 -
MyButton❌ 含大写字母 -
user_profile❌ 含下划线 -
div❌ 与原生标签重名
别指望 try/catch 捕获——错误发生在注册阶段,组件根本没机会初始化。
DOM 操作和属性读取必须放在 connectedCallback
很多人把渲染逻辑全塞进 constructor,结果发现 this.shadowRoot 是 null、样式不生效、this.getAttribute('config') 返回 null。因为 constructor 执行时元素尚未挂载到文档,DOM 上下文不可用。
立即学习“前端免费学习笔记(深入)”;
-
constructor只做三件事:调用super()、调用this.attachShadow({ mode: 'open' })、声明私有字段或绑定方法 - 所有 DOM 查询(如
this.shadowRoot.querySelector)、事件监听、innerHTML设置、fetch请求,都必须移入connectedCallback -
attributeChangedCallback不会触发初始属性值,首次渲染仍需在connectedCallback中手动读取this.getAttribute('config')
样式必须注入 Shadow DOM,不能靠外部 CSS
把 <link rel="stylesheet"> 放在自定义元素内部,或试图用 @import 加载外部样式,基本无效。Shadow DOM 的样式隔离机制决定了:样式必须显式写入 shadowRoot.innerHTML 或通过 adoptedStyleSheets 注入(后者需现代浏览器支持)。
- 最稳妥做法:在
connectedCallback中拼接<style></style>字符串并插入shadowRoot - 样式复杂时,可预编译为字符串常量(如
const STYLES = `...`),避免运行时重复解析 - 禁止依赖全局 CSS 类名,否则无法保证封装性;
.btn这类选择器在 Shadow DOM 外部定义,对组件内元素完全无效
如何用 attributeChangedCallback 实现插件参数通信
父应用向子组件传参不能靠自动类型转换,因为所有 HTML attribute 都是字符串。典型误用:<micro-app config='{"theme":"dark"}'/> → 子组件里 this.config 是字符串,不是对象。
- 只监听明确声明的
observedAttributes,如['config', 'env'],避免无谓触发 - 在
attributeChangedCallback(attrName, oldVal, newVal)中对关键属性做JSON.parse或类型转换 - 若值为布尔或数字,需手动转换:
const value = newVal === 'true' ? true : newVal === 'false' ? false : +newVal || newVal - 这是跨框架通信唯一安全出口——React/Vue 子应用可通过该回调接收配置,再触发其内部 mount 流程
真正难的不是注册一个元素,而是让它的生命周期与外部框架(React/Vue)对齐,同时确保样式不泄漏、资源不重复加载、卸载时不内存泄漏——这些都得靠 connectedCallback 和 disconnectedCallback 手动桥接,没有银弹。



















