1. Slot API核心概念:插槽定义、命名与默认内容、作用域
Slot(插槽)是组件化框架中实现内容分发与组合的核心机制。在 Material 3 生态中,无论是基于 Lit 的 @material/web 库,还是 Vue / React 的二次封装,插槽都承担着“定义组件可变内容区域”的角色。本章从底层原理出发,依次解析插槽的三种基本形态:默认插槽、命名插槽和作用域插槽,并给出对应代码实现与常见调试技巧。
1.1 插槽的三要素定义
| 概念 | 含义 | 使用场景 |
|---|---|---|
| 默认插槽 | 组件模板中未指定 name 的 <slot> 元素,接收所有未匹配的投影内容 | 按钮文本、卡片正文、弹窗主体 |
| 命名插槽 | 通过 name 属性标记的 <slot name="xxx">,只接收同名的 slot="xxx" 投影内容 | 弹窗标题/脚注、列表项前缀/后缀 |
| 作用域插槽 | 允许子组件将内部数据通过 slot 属性暴露给父级模板,父级可自定义渲染逻辑 | 表格行自定义、下拉选项格式化、动态列表 |
1.2 插槽的实现流程
以 Lit 组件(对应 Material 3 Web Components 官方实现)为例,定义与使用插槽分为四步:
- 声明插槽:在组件的 render 模板中使用
<slot></slot>(默认)或<slot name="title"></slot>(命名)。 - 提供默认内容:在
<slot>标签内部写入回退元素,当父级未投影时显示。 - 投影内容:父级使用组件时,将子元素或模板放置在组件标签内,并通过
slot="xxx"属性匹配命名的插槽。 - 作用域传递:在子组件中通过
@slotchange事件捕获插槽变化,或使用 Lit 的map+html动态渲染具名插槽数据;在 Vue 中使用v-slot指令接收作用域对象。
1.3 代码示例:三种插槽的定义与使用
以下基于 Lit 3.x 实现一个 Material 3 风格的 Card 组件,完整演示默认、命名与作用域插槽。
// card.ts - 使用 Lit 定义的 Material 3 Card 组件
import { LitElement, html, css } from 'lit';
import { customElement } from 'lit/decorators.js';
@customElement('m3-card')
export class M3Card extends LitElement {
static styles = css`
:host { display: block; padding: 16px; border-radius: 12px; box-shadow: 0 2px 8px rgba(0,0,0,0.1); }
.header { font-size: 1.2em; margin-bottom: 8px; }
.footer { margin-top: 16px; border-top: 1px solid #eee; padding-top: 8px; }
`;
// 默认插槽 + 命名插槽 + 作用域数据
render() {
return html`
<div class="header"><slot name="title">默认标题</slot></div>
<div class="body"><slot>默认正文内容</slot></div>
<div class="footer">
<slot name="actions">
<!-- 提供默认操作按钮 -->
<button>确定</button>
</slot>
</div>
<!-- 作用域插槽通过属性暴露内部数据 -->
<slot name="item" .data=${{ id: 1, label: '示例' }}></slot>
`;
}
}
父级使用组件并投影内容:
<m3-card>
<span slot="title">产品说明</span>
<p>这是一张基于 Material 3 的卡片,内容由父级提供。</p>
<button slot="actions">取消</button>
<!-- 作用域插槽:父级可通过 slot 获取子组件暴露的数据 -->
<template slot="item" slot-scope="data"> <!-- 注:Lit 原生不支持 slot-scope,此为示意;实际需配合 @slotchange 或 Lit 的 repeat 指令 -->
<span>${data.id} - ${data.label}</span>
</template>
</m3-card>
提示: Lit 的原生 slot 不支持作用域插槽语法。实际 Material 3 组件库(如
@material/web)通常通过属性绑定或事件回调来实现类似作用域的效果。在 Vue 中则直接使用 v-slot 指令,写法更简洁。1.4 作用域插槽的常见实现方式
在不同框架中,作用域插槽的实现路径有所差异:
| 框架/库 | 实现方式 | 示例 |
|---|---|---|
| Vue 3 | v-slot:item="{ data }" | <template v-slot:item="{ id }">{{ id }}</template> |
| Lit | 通过 @slotchange 事件获取插槽元素,或使用 map+html 动态渲染 | this.shadowRoot.querySelector('slot[name="item"]')?.assignedNodes() |
| React | 使用 render props 模式:组件的 props 接收一个函数,由子组件调用并传入数据 | <Card renderItem={(id) => <span>{id}</span>} /> |
实现关键: 无论框架如何变化,作用域插槽的本质都在于“子组件将内部数据暴露给父级的模板作用域”,父级获得数据后可以决定如何渲染。理解这一点后,在任何框架中都能快速切换。
1.5 调试与常见错误
- 错误:插槽未被渲染——检查父级投影元素是否包含匹配的
slot属性,注意属性名大小写(推荐全小写)。 - 错误:默认内容不显示——确认
<slot>标签内确实写入了子元素,并且父级没有提供相同名称的投影内容(空内容也算)。 - 错误:作用域数据未传递——在 Lit 中,直接写
<slot .data=${{...}}>并不会自动暴露到父级作用域,需要配合@slotchange或使用 Lit 的repeat指令手动遍历。 - 调试技巧:在浏览器开发者工具中查看 Shadow DOM 的
slot节点,观察assignedNodes列表,可以快速定位投影内容是否匹配。
1.6 Slot 结构关系图
下图直观展示了 Material 3 组件中默认插槽、命名插槽和作用域插槽的协作关系:
注意: 上图作用域插槽的“带数据”仅为概念示意。在实际 Lit 组件中,作用域数据通常通过
@slotchange 事件或组件属性向父级传递,而非直接在 <slot> 标签上挂载。请结合具体框架的 API 文档验证。1.7 默认内容的最佳实践
为插槽提供合理的默认内容能提升组件的即用性。原则:
- 默认文本应简洁,避免占位符过长影响布局。
- 当默认内容包含交互元素(如按钮)时,务必绑定正确的事件处理函数。
- 在 Material 3 中,默认内容应遵循组件的语义角色:例如按钮默认显示“确定”,弹窗默认无标题则显示空字符串。