Shadow DOM 本身不构成 Web 组件,仅提供封装能力;真正自包含的组件必须由 customElements.define() 注册的自定义元素驱动,需主动处理内容分发、样式作用域和生命周期。

Shadow DOM 本身不构建“Web文档组件”,它只提供封装能力;真正自包含的组件必须由 customElements.define() 注册的自定义元素驱动,且需主动处理内容分发、样式作用域和生命周期。
为什么不能直接用 attachShadow() 给普通 <div> 做文档组件
你可以在任意合法宿主(如 <div>、<section>)上调用 attachShadow(),但这只是挂载了一棵私有子树——它没有语义、没有可复用性、无法被 HTML 声明式使用,也不响应 DOM 生命周期事件。浏览器不会把它当作“组件”,只是个带隔离 DOM 的普通元素。
常见错误现象:document.querySelector('div').attachShadow({mode: 'open'}) 后,该 <div> 在 HTML 中仍写成 <div></div>,无法像 <my-card></my-card> 那样被识别、复用或跨项目嵌入。
- 真正可用的文档组件必须是自定义元素(类继承
HTMLElement) -
attachShadow()是构造函数里的一环,不是独立解决方案 - 原生元素(如
<input>、<video>)虽自带 Shadow DOM,但不可脚本化接管其内部结构
connectedCallback() 里操作 shadowRoot 才算真正启动
Shadow DOM 在 constructor() 中创建后,节点尚未插入文档,此时 shadowRoot.querySelector() 可能返回 null,事件监听也无效。必须等元素挂载到 DOM 后,才能安全访问和交互。
典型误用:this.shadowRoot.querySelector('button').addEventListener(...) 放在 constructor 中,结果监听未生效。
- DOM 操作、事件绑定、动态内容注入统一移至
connectedCallback() - 若需响应属性变更,配合
static get observedAttributes()+attributeChangedCallback() - 避免在
constructor中做任何依赖shadowRoot实际渲染的操作
用 <slot> 接收外部内容,而不是靠 innerHTML 注入
把用户写的 HTML 片段硬塞进 shadowRoot.innerHTML,会丢失原始节点的事件绑定、id 引用、表单关联(如 label[for]),且破坏可访问性。正确方式是声明插槽,让外部内容“流”进来。
例如:用户写 <my-doc title="指南"><h2>快速开始</h2><p>第一步...</p></my-doc>,组件内部应通过 <slot> 渲染,而非字符串拼接。
-
<slot name="title"></slot>对应具名插槽,匹配<h2 slot="title"> - 默认插槽
<slot></slot>自动承接无slot属性的子节点 -
::slotted(*)可在 Shadow DOM 内部为插槽内容设样式,但无法穿透到子组件内部 - 避免在
shadowRoot.innerHTML = `...`中拼接用户传入的 HTML 字符串
样式隔离不是万能的,:host 和 ::part() 才控制对外接口
Shadow DOM 内部样式默认不泄漏,但组件常需暴露部分样式钩子供外部定制(比如主题色、尺寸)。纯靠 <style> 块写死样式,会让组件僵化。
容易踩的坑:外部用 my-doc h2 { color: red; } 试图改标题颜色——完全无效,因为 h2 在 Shadow DOM 内,外部选择器无法命中。
-
:host选中组件自身(即<my-doc>元素),可用于设置 display、margin 等宿主级样式 -
:host([size="large"])响应属性变化,实现条件样式 -
exportparts="title, content"配合::part(title)允许外部精确定制特定区域 - 慎用
/deep/或::ng-deep(已废弃),它们破坏封装边界
最易被忽略的点:Shadow DOM 不解决数据流或状态管理——它只管 DOM 和样式封装。一个真正自包含的文档组件,还需自行处理属性映射、事件派发(this.dispatchEvent(new CustomEvent(...)))、无障碍属性(role、aria-)补全,以及 SSR 兼容性(服务端不执行 JS,attachShadow 会失败)。这些不在 Shadow DOM 范畴内,但缺一不可。

















