customElements.define()必须同步执行,所谓“异步加载”实为控制注册时机+手动upgrade已存在节点;它只接受同步class构造函数,不支持Promise或工厂函数,否则报TypeError。

customElements.define() 必须同步执行,所有“异步加载自定义元素”的实际做法,都是在控制注册时机 + 手动升级已有节点,而不是让 define() 本身变异步。
为什么 customElements.define() 不能接 Promise
浏览器强制要求第二个参数必须是**同步返回的 class 构造函数**。常见错误写法:customElements.define('x-chart', () => import('./chart.js').then(m => m.XChart)),会直接报 TypeError: Class constructor XChart cannot be invoked without 'new'。因为 import() 返回的是 Promise,而 define() 不接受函数工厂或 Promise —— 它只认裸 class。
动态导入后必须显式 customElements.define() 再 upgrade()
只 await import() 不够,必须紧接着调用 customElements.define(),否则 customElements.whenDefined('x-chart') 永远不会 resolve,后续 upgrade() 也无效。
- 先确保 HTML 中已存在
<x-chart>节点(SSR 输出或静态写死) - 模块加载完成后,顺序执行:
customElements.define()→customElements.whenDefined()→customElements.upgrade() -
upgrade()只对当前已挂载的节点生效;新插入的节点会自动升级(前提是已定义) - Shadow DOM 内部的
x-chart实例不会被document.querySelectorAll('x-chart')匹配到,需手动遍历 shadowRoot
自定义元素内部发起异步请求的正确时机
不能在 connectedCallback() 里无条件发请求——节点可能反复挂载/卸载,导致重复 fetch 或竞态。推荐模式:
- 在
constructor初始化状态(如this._fetched = false) - 在
connectedCallback中检查this._fetched或this.hasAttribute('data-loaded'),为 false 才发起请求 - fetch 必须
try/catch,且显式检查response.ok,不要只靠 status !== 0 判定失败 - 若依赖其他自定义元素(如
x-api-client),必须先await customElements.whenDefined('x-api-client'),再操作
事件监听和属性变更必须手动绑定与响应
customElements.define() 只注册标签,不自动绑定任何行为。常见坑点:
立即学习“前端免费学习笔记(深入)”;
-
constructor里查this.shadowRoot.querySelector('button')会返回null,因为此时 shadowRoot 虽已创建,但节点尚未挂载;监听器必须放connectedCallback -
attributeChangedCallback只响应setAttribute()、HTML 属性初始值、removeAttribute(),对 JS 属性赋值(如el.disabled = true)完全无感 - 要支持 JS 属性同步更新 UI,得在 setter 里手动调用
setAttribute(),并在observedAttributes中声明该属性名 - 布尔属性的
newValue是字符串"true"或null,不是布尔值,需转换



















