自定义元素注册必须在 domcontentloaded 后执行,标签名须含短横线且全小写,类须继承 htmlelement 并调用 super(),属性传字符串或布尔值,样式通过 css 自定义属性暴露,slot 需明确定义并注释。

自定义元素不是“锦上添花”的玩具,它直接决定你能不能把组件接口写清楚、样式不乱窜、协作不扯皮——前提是它被当作契约来用,而不是随手注册一个标签就完事。
customElements.define 必须在 DOMContentLoaded 后执行
注册太早会失败,但很多人误以为加了 defer 就万事大吉。实际常见错误是:脚本放在 里且没监听事件,浏览器解析到 <my-button></my-button> 时类还没定义,结果渲染成空白或 fallback 内容。
- 必须等
document.readyState === 'interactive'或显式监听DOMContentLoaded,尤其在模块未打包、直接 script 引入时 - 如果用构建工具(如 Vite/Rollup),确保自定义元素模块被标记为
type="module",否则customElements.define可能因执行时机不可控而静默失败 - 注册失败无报错,只会在控制台看到
Unknown element: my-button类似提示,需主动检查customElements.get('my-button')是否返回构造函数
属性只能传字符串或布尔值,别塞对象或函数
传 config 对象看着方便,但 SSR 渲染时会变成 [object Object],服务端根本没法序列化;传函数更危险,不仅无法序列化,还破坏 Shadow DOM 的封装边界。
- 复杂配置统一走
data-属性,比如<date-picker data-config='{"format":"YYYY-MM-DD"}'></date-picker>,然后在connectedCallback里JSON.parse(this.getAttribute('data-config')) - 布尔属性严格用
observedAttributes声明,并在attributeChangedCallback中用this.hasAttribute('disabled')判断,而不是this.getAttribute('disabled') === 'true' - 禁止在构造函数里读取属性值——此时属性尚未设置,
this.getAttribute返回null
Shadow DOM 不等于自动样式安全
用了 attachShadow 就以为样式不会被污染?错。过度依赖 :host 和 ::slotted 会导致选择器优先级混乱,嵌套三层后连自己都改不动颜色。
-
:host只用于控制根节点状态(如:host([open])),内部子元素样式全部写在 shadow 内部,不穿透、不依赖外部 class - 对外暴露定制点:在
:host中声明--button-bg、--button-color等 2–3 个高频 CSS 自定义属性,文档里写明用途,别让下游开发者只能改源码 - 慎用
::slotted(*)——它会匹配所有插入内容,包括深层嵌套的子元素,极易意外覆盖第三方组件样式
slot 名称必须在注释里明确定义
不写 slot 文档,下游开发者只能靠猜或翻源码。有人往 <alert-banner></alert-banner> 里塞 <div slot="icon">,结果发现作者根本没预留这个 slot,内容直接消失。<ul>
<li>每个自定义元素类顶部加 JSDoc 注释,例如 <code>/** @slot icon - 左侧图标区域 */
default,但别默认让它承载所有内容;明确拆分 header、footer、actions 等语义 slot,降低使用歧义<slot></slot> 去包裹另一个自定义元素,这会让 slot 分发逻辑变得不可预测最难的不是写出来,而是让所有人按同一份契约去用——比如 value 属性到底触发更新还是只做初始化,change 事件是否冒泡,disabled 是否禁用内部 <button></button>。这些细节一旦没对齐,自定义元素就从协作利器退化成新的冲突源头。











