slotchange 仅在 light dom 直系子元素增删或重排序时触发,不响应内部内容变化;首次渲染需在 slotchange 回调中获取 assignednodes({flatten:true}),lit 中推荐用 updated() 替代手动监听。

slotchange 事件只在分配关系变更时触发,不是内容更新的万能监听器
slotchange 不会响应 slot 内部节点的文本变化、属性修改或子树变动,它只在 Light DOM 中带 slot 属性的直系子元素被新增、移除或重排序时触发。比如你动态执行 el.appendChild(document.createElement('h2')).setAttribute('slot', 'header'),才会触发;但改 h2.textContent = '新标题' 不会。
常见误判现象:slotchange 监听器没执行,但控制台看到 slot 内容明明变了——大概率是改了节点内部状态,而非 slot 分配关系本身。
- 必须在
connectedCallback或adoptedCallback中绑定,不能在constructor里(此时shadowRoot还未存在) - 监听目标必须是具体的
slot元素,例如this.shadowRoot.querySelector('slot[name="header"]'),不能监听整个shadowRoot - 该事件不冒泡,也不捕获,
event.stopPropagation()和事件委托都无效
为什么首次渲染时拿不到 assignedNodes()?别在 connectedCallback 里直接查
Light DOM 的解析和 slot 分发是异步完成的。即使你在 connectedCallback 中立刻调用 slot.assignedNodes(),返回的很可能是空数组或不完整列表——因为外部 HTML 还没被浏览器解析完毕。
正确做法是把初始化逻辑放进 slotchange 回调里,而不是依赖挂载时机:
this.shadowRoot.querySelector('slot[name="header"]').addEventListener('slotchange', () => {
const nodes = this.shadowRoot.querySelector('slot[name="header"]').assignedNodes({ flatten: true });
if (nodes.length > 0) {
// 这里才真正拿到有效节点
}
});
- 务必加
{ flatten: true }参数,否则跨多层嵌套 slot(比如具名 slot 里又用了默认 slot)的内容会被忽略 - Firefox slotchange,需降级用
MutationObserver监听slot元素的childList变化 - 如果组件可能被多次连接/断开,记得在
disconnectedCallback中移除监听器,避免内存泄漏
如何响应 slot 内部的文本或属性变化?slotchange 不够用,得加 MutationObserver
当你需要监听 slot 分配进来的节点内部变化(如用户输入导致 <input slot="form-field"> 的 value 改变),slotchange 完全无能为力。此时必须对已分配的节点手动挂载 MutationObserver。
但要注意:观察对象是 assignedNodes() 返回的节点,不是 slot 元素本身;且需在每次 slotchange 后重新建立观察,因为分配节点可能已更换:
this.shadowRoot.querySelector('slot[name="form-field"]').addEventListener('slotchange', () => {
const nodes = this.shadowRoot.querySelector('slot[name="form-field"]').assignedNodes({ flatten: true });
nodes.forEach(node => {
if (node.nodeType === Node.ELEMENT_NODE && node.tagName === 'INPUT') {
new MutationObserver(() => this.handleInputUpdate()).observe(node, { attributes: true, attributeFilter: ['value'] });
}
});
});
- 代价高:每个被观察节点都会额外消耗资源,慎用于大量 slot 或高频更新场景
- 注意清理:在
disconnectedCallback或下次slotchange前,调用observer.disconnect() - 纯文本节点(Text)无法被
MutationObserver观察属性,只能监听其父元素的characterData变化,实际中更推荐用事件代理(如监听input、change)
使用 Lit 时,updated() 钩子比 slotchange 更适合做响应式判断
Lit 组件中,updated() 在每次更新周期结束时调用,天然适合对比前后 assignedNodes() 差异。相比手动监听 slotchange,它更贴近框架语义,也更容易统一处理多个 slot 的响应逻辑。
示例中,不需要提前绑定事件,只需在 updated() 里检查:
updated(changedProperties) {
super.updated(changedProperties);
const headerSlot = this.shadowRoot.querySelector('slot[name="header"]');
const currentNodes = headerSlot?.assignedNodes({ flatten: true }) || [];
if (changedProperties.has('headerNodes') && !arraysEqual(this.headerNodes, currentNodes)) {
this.headerNodes = currentNodes;
this.requestUpdate(); // 如需二次更新
}
}
- 别直接在
render()里调用assignedNodes()—— 它可能返回空,且会破坏 Lit 的静态模板优化 - 若 slot 内容含 Lit 子组件,它们的
@property更新不会触发父组件updated(),必须靠@state或显式requestUpdate() - 注意:Lit 的
updated()不保证节点已真实挂载到 Shadow DOM,DOM 操作仍建议放在firstUpdated()中
slotchange,后者得靠 MutationObserver 或事件代理。混淆这两者,是绝大多数 slot 动态响应失效的根源。前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











