customElements.define() 必须等待第三方库全局变量就位,否则图表白屏、报错或内存泄漏;需按依赖顺序串行加载、显式调用 update/destroy、避免 Shadow DOM 破坏渲染上下文,并在 disconnectedCallback 中彻底清理实例与副作用。

不能直接用 customElements.define() 注册就完事——第三方图表库(如 Chart.js、Lightweight-charts)没加载完,或 Shadow DOM 隔离了渲染上下文,组件必然白屏、报错或内存泄漏。
customElements.define() 必须等第三方库全局变量就位
浏览器报 Failed to execute 'define' on 'CustomElementRegistry': the name "my-chart" has already been used 或图表不渲染,90% 是因为 customElements.define() 被提前执行了。
- Chart.js 依赖
window.Chart,Leaflet 依赖window.L,Lightweight-charts 依赖window.LightweightCharts—— 这些变量必须存在才能初始化实例 - 不要在
<script>标签内同步调用customElements.define();改用document.addEventListener('DOMContentLoaded', ...)+ 轮询检查if (window.Chart) - 若用 ES 模块动态加载,写成
import('chart.js').then(() => customElements.define('my-chart', ChartComponent)) - 多个图表库共存时(比如同时用 Chart.js 和 Lightweight-charts),需按依赖顺序串行加载,避免竞态
属性变更必须触发第三方实例的 update / applyOptions
只监听 data-config 并重写 shadowRoot.innerHTML,图表不会更新——第三方库实例(this._chart 或 this._chartApi)必须被显式调用。
-
static get observedAttributes()至少包含['data-config', 'data-series'],但attributeChangedCallback里不能只做 JSON 解析 - 首次初始化走
new Chart(ctx, config);后续变更优先调用this._chart.data.datasets = newDatasets+this._chart.update()(Chart.js)或this._chartApi.timeScale().fitContent()(Lightweight-charts) - 避免每次解析
JSON.parse(this.getAttribute('data-config')),应在connectedCallback中缓存为this._config = {...} - Leaflet 类型组件必须在更新前调用
this._map.remove(),否则图层叠加、内存泄漏
Shadow DOM 会破坏 Canvas / div 渲染容器的尺寸和样式链
把 <canvas> 或 <div id="chart"></div> 放进 this.attachShadow({ mode: 'open' }),Chart.js 报 Canvas is not initialized,Lightweight-charts 直接静默失败——这不是 bug,是 Shadow DOM 切断了 CSS 继承与 getBoundingClientRect() 查询路径。
立即学习“前端免费学习笔记(深入)”;
- 绝大多数图表库要求容器有明确宽高、非
display: none、且能被document.querySelector或父级offsetWidth正确测量;Shadow DOM 内部节点无法被外部脚本访问 - 方案一:放弃 Shadow DOM,用 light DOM + 命名空间 class(如
<div class="my-chart__container">),靠 CSS BEM 约束样式泄露 - 方案二:若必须用 Shadow DOM,得手动透传容器尺寸——监听
ResizeObserver并通过this.parentElement.getBoundingClientRect()同步给内部 canvas,再调用ctx.canvas.width = width等重设 - Mapbox GL、ECharts 的 WebGL 渲染器明确禁止 Shadow DOM,文档里写了“not supported in shadow DOM”
disconnectedCallback 里必须销毁实例与副作用资源
页面切换、Vue/React 组件卸载、el.remove() 后图表还在后台跑定时器、监听 window.resize、甚至持续请求 WebSocket 数据——这是典型内存泄漏。
-
disconnectedCallback是唯一可靠的清理入口,必须显式调用:this._chart.destroy()、this._map.remove()、this._chartApi.remove() - 清除所有事件监听:
window.removeEventListener('resize', this._onResize),别忘了用箭头函数绑定的监听器无法 remove,得存为成员变量 - 清除定时器:
clearInterval(this._pollTimer)、clearTimeout(this._debounceId) - 如果用了 MutationObserver 或 IntersectionObserver,也得在这里
.disconnect()
真正难的不是注册一个标签,而是让这个标签在 SPA 路由跳转、SSR hydration、多次 mount/unmount 场景下不崩、不漏、不卡——所有清理逻辑必须可逆且幂等,第三方库的 destroy 接口是否真正释放 GPU 资源,得看它源码里有没有 gl.deleteTexture 或 cancelAnimationFrame 这类调用。



















