slot属性仅为大小写敏感的字符串匹配标签,不传输数据;light dom节点以引用形式投影至shadow dom的slot位置,保持原dom树结构、事件路径和响应式更新。

slot 属性本身不携带数据,它只是内容分发的“路由标签”;真正流动的是 Light DOM 节点的引用,且全程不脱离原始 DOM 树。
slot 属性只参与匹配,不传输值或对象
slot 是一个纯字符串标识符,浏览器仅用它做一次性的、大小写敏感的键比对。它不会把属性值转成 JS 数据、不触发 getter/setter、不序列化、不绑定响应式逻辑。
-
<h2 slot="header">标题</h2>中的"header"仅用于匹配<slot name="header"></slot>,匹配成功后,<h2></h2>节点本身(含所有子节点、属性、事件监听器)被整块“投影”,不是复制也不是移动 - 如果你写
<div slot="{{title}}"></div>(比如在 Vue 模板里),Vue 会先求值并写死为slot="user-info",但浏览器只认最终渲染出的字符串,不理解双大括号 - 不能靠
slot传函数、状态或组件实例——它连字符串都不能动态更新:改el.slot = "footer"不会触发重分发,必须替换整个节点或触发slotchange
数据流向本质是“节点引用投射”,不是 DOM 移动
Light DOM 中的节点始终保留在原位置,slot 只是告诉浏览器:“这个节点,在渲染时,请把它画在 Shadow DOM 的这个 <slot></slot> 占位处”。这意味着:
- 调用
document.querySelector('h2')仍能拿到它,parentNode仍是外部自定义元素,不是shadowRoot - 给该节点加
className或修改textContent,会立刻反映在投影位置,因为操作的是同一个 DOM 对象 - 事件冒泡路径走的是 Light DOM 链:点击
<h2 slot="header"></h2>,事件从h2 → my-card → body,不经过shadowRoot内部节点 -
slot.assignedNodes()返回的也是这些原始节点引用,不是克隆副本
为什么 assignedNodes() 有时为空?时机和 flatten 是关键
分发不是同步完成的:DOM 插入、slot 匹配、投影映射发生在浏览器渲染流水线的特定阶段,JS 无法立即读取结果。
- 在
connectedCallback或firstUpdated(Lit)中直接调slot.assignedNodes(),大概率返回空数组——分发尚未开始 - 正确做法是监听
slotchange事件:slot.addEventListener('slotchange', () => { ... });注意它不冒泡,也不能在shadowRoot上监听 -
assignedNodes()默认只返回直系分配节点,嵌套在 fallback 里的内容、或经由子组件<slot></slot>二次分发的节点,必须加{ flatten: true }才能拿到 - Firefox 63 以下不支持
slotchange,需降级用MutationObserver监听slot元素的childList
::slotted() 样式穿透只作用于投射入口,不控制深层结构
::slotted() 是唯一能影响 Light DOM 内容样式的机制,但它能力边界非常明确:它只匹配“被当前 <slot></slot> 直接接收”的那一层节点。
-
::slotted(h2)✅ 匹配<h2 slot="header"></h2>;::slotted(.title)✅ 匹配<p class="title" slot="content"></p> -
::slotted(div h2)❌ 失效:中间的<div> 是 Light DOM 子节点,但未被该 <code><slot></slot>接收,所以不在投射路径上 -
::slotted(my-button span)❌ 即使<my-button></my-button>被投射进来,它的内部结构仍属其自身 Shadow DOM,::slotted()无法穿透两层 Shadow 边界 - 想控制深层样式,只能靠 CSS 自定义属性(如
--card-header-color)向下透传,或要求使用者显式加class
最容易被忽略的是:分发发生在渲染前的投影阶段,不是 JS 可干预的运行时过程。你无法用 JS “阻止”某个节点被投射,也无法在分发中途修改它的 slot 属性来切换目标——一旦节点插入,匹配就已确定,重分发只发生在节点增删时。











