Stencil 嵌套 slot 组件实战:以 slot-parent-cmp 为例解析插槽转发、默认插槽与自动文档生成
2026/9/23 14:28:59 网站建设 项目流程
  • 开发工具
  • 前端
  • 前端构建

【免费下载链接】stencil

A toolchain for building scalable, enterprise-ready component systems on top of TypeScript and Web Component standards. Stencil components can be distributed natively to React, Angular, Vue, (+ more) and traditional web applications from a single, framework-agnostic codebase.

项目地址:https://gitcode.com/gh_mirrors/st/stencil
点击查看免费下载

导读

本文以 Stencil 仓库 test/end-to-end 端到端测试套件中的 slot-parent-cmp 组件为实例,系统讲解 Stencil 组件中slot(插槽)的声明方式、嵌套转发机制、默认插槽的透传,以及docs-readme输出目标如何自动生成组件 API 文档与依赖关系图。读完本文,你将掌握 Stencil 中「组件声明<slot />→ 上层组件转发子内容 → Shadow DOM 渲染 → e2e 断言验证」的完整链路,并能读懂仓库中任意组件自动生成的readme.md

说明:test/end-to-end/src/slot-parent-cmp/readme.md是 Stencil 通过docs-readme输出目标自动生成的组件文档,属于"组件 API 快照",其信息完全来源于组件源码元数据,因此本文以该自动文档为骨架,结合源码、运行时实现与 e2e 测试展开解读。

一、自动生成的组件文档:readme.md 里有什么

slot-parent-cmp的 readme.md 由编译器自动生成,包含四个固定小节:Properties(属性)、Slots(插槽)、Dependencies(依赖关系)、以及依赖图(Mermaid)。这类文档的生成逻辑位于 src/compiler/docs/readme 目录,例如 markdown-slots.ts 会把组件元数据中的插槽信息渲染成## Slots表格:

  • 表头固定为Slot | Description
  • 默认插槽(name为空字符串)在表格中表现为空白的 Slot 单元格;
  • 具名插槽则会以`"name"`的形式输出。

也就是说,本文档中 "The default slot" 这一行,正是编译器从 slot-parent-cmp.tsx 源码中提取出的插槽声明生成的。readme.md第一行的# slot-parent-cmp标题也来自组件tag

Properties:组件公开属性一览

文档的 Properties 表格如下:

PropertyAttributeDescriptionTypeDefault
labellabelstringundefined

这对应源码中的一行声明:

@Prop() label: string;

PropertyAttribute同列展示说明该 Prop 未显式配置attribute别名,因此默认使用与属性同名的 HTML 属性label。类型string与默认值undefined均来自 Stencil 的静态类型分析。该属性在渲染中被直接输出到组件根节点:

render() { return ( <Host> {this.label} ... </Host> ); }

即当使用者写入<slot-parent-cmp label="One" />时,组件根元素内会渲染出文本节点One,随后才是插槽内容。这为下文要讲的插槽顺序问题埋下了伏笔。

二、组件源码:一次完整的"插槽转发"示例

slot-parent-cmp.tsx 的全部实现只有十几行,但完整演示了 Stencil 插槽的核心用法:

import { Component, Host, Prop, h } from '@stencil/core'; @Component({ tag: 'slot-parent-cmp', }) export class SlotParentCmp { @Prop() label: string; render() { return ( <Host> {this.label} <slot-cmp> <slot /> </slot-cmp> </Host> ); } }

要点拆解:

  1. <slot />声明默认插槽:在 JSX 中直接书写<slot />,表示该组件接收并渲染父级传入的子内容(light DOM)。由于未指定name,它接收的是"默认插槽"内容。
  2. 插槽转发(slot forwarding)<slot />被包裹在<slot-cmp>内部,也就是说slot-parent-cmp把"接收到的子内容"作为slot-cmp的默认插槽内容继续向下传递。这就是文档 Dependencies 中slot-parent-cmp --> slot-cmp这条边对应的运行时行为。
  3. Host根节点承载文本{this.label}渲染在Host内、<slot-cmp>之前,是插槽之外的内容。

这一"插槽再插槽"的嵌套结构正是仓库中 nested-slot-forwarding.spec.tsx 所覆盖的运行时场景——Stencil 的虚拟 DOM 渲染器会把最内层<slot />的内容投影到最终落点,而不是在中间组件处"卡住"。

三、上下游组件:插槽在真实使用中的位置

3.1 上游:slot-cmp-container如何投喂插槽内容

slot-parent-cmp的唯一使用者是 slot-cmp-container,后者是一个shadow: true的 Shadow DOM 组件,渲染结构如下:

<Host> <slot-cmp> <slot-parent-cmp label="One" /> </slot-cmp> <slot-cmp> <slot-parent-cmp label="Two" /> </slot-cmp> <slot-cmp> <slot-parent-cmp label="Three" /> </slot-cmp> </Host>

这里形成了双层传递:slot-cmp-container<slot-parent-cmp>元素作为slot-cmp的默认插槽内容;而每个slot-parent-cmp又会把"自身收到的子内容"继续转给它的slot-cmp子组件。componentDidLoad中还会执行一次forceUpdate(this.host),用于验证强制刷新后插槽投影依然保持正确顺序。

3.2 下游:slot-cmp是插槽的最终落点

slot-cmp.tsx 是最底层的容器,仅声明了一个默认插槽:

@Component({ tag: 'slot-cmp', styles: 'slot-cmp { display: inline-block; }', }) export class SlotCmp { render() { return ( <Host> <slot /> </Host> ); } }

整条链路可以概括为:

slot-cmp-container(shadow) └─ slot-cmp(内含 <slot/>) └─ slot-parent-cmp(内含 <slot/> 转发给下一层) └─ slot-cmp(内含 <slot/>,最终落点)

标签文本One/Two/Three(来自labelProp)与插槽内容最终都会渲染到最内层slot-cmp的默认插槽位置。

四、依赖关系与 Mermaid 依赖图

slot-parent-cmp的自动文档给出了完整的依赖信息:

  • Used byslot-cmp-container(即谁在模板中使用了该组件)
  • Depends onslot-cmp(即该组件的模板引用了哪些自定义元素)
  • Graph:由上述两个方向合并而成的 Mermaid 依赖图

这张图与 slot-cmp-container/readme.md 中的依赖图互为印证:容器同时依赖slot-cmpslot-parent-cmp,而slot-parent-cmp依赖slot-cmp,因此容器文档的图中有slot-cmp-container --> slot-cmpslot-cmp-container --> slot-parent-cmp两条边。style行只是 Markdown 生成器的视觉标记,用于高亮"当前文档所属组件"节点。

依赖图的生成逻辑可追溯至 src/compiler/docs/readme/markdown-dependencies.ts(Dependencies 小节与 Mermaid 图),它依赖编译期收集的组件依赖元数据——即编译器在分析 JSX 模板时,为每个组件记录下它引用的其他自定义元素集合。

五、插槽顺序的正确性验证:e2e 测试

自动文档不会告诉你"插槽是否正确渲染",这由 e2e 测试保证。slot-cmp-container.e2e.ts 用 Stencil 测试运行时newE2EPage起了一个真实浏览器页面:

import { newE2EPage } from '@stencil/core/testing'; describe('Slots', () => { it('should render the slots in the correct order', async () => { const page = await newE2EPage({ html: '<slot-cmp-container></slot-cmp-container>' }); const element = await page.find('slot-cmp-container'); expect(element.shadowRoot.textContent).toContain('OneTwoThree'); }); });

断言textContent包含连续的OneTwoThree,这同时验证了三件事:

  1. 三个slot-parent-cmplabel文本按文档顺序渲染;
  2. 每层<slot />转发没有丢失或乱序内容;
  3. Shadow DOM 的插槽分配(slot assignment)在组件树嵌套后依然正确。

这正是仓库中大量 wdio 插槽测试(如 slot-basic、slot-nested-order、slot-reorder 等)所覆盖主题的 e2e 侧缩影。若想深入底层机制,可继续阅读 dom-extras.ts(其中包含针对 slotted 节点在 scoped 组件中的prepend/append/insertAdjacent*等方法的修补实现)以及 vdom-render.ts 中的插槽投影逻辑。

六、如何在自己的组件库中复现这套"自动文档 + 插槽"

6.1 开启 docs-readme 输出

自动生成readme.md依赖 Stencil 配置中的docs-readme输出目标。仓库 test/end-to-end/stencil.config.ts 即为端到端测试项目启用了该输出目标;组件源码目录下的readme.md头部注释<!-- Auto Generated Below -->就是其产物标记。运行stencil build时,编译器会扫描所有组件并刷新对应文档。

6.2 撰写插槽时遵循的实践

  • 默认插槽:直接写<slot />,文档 Slots 表格中 Slot 列为空白,Description 可写 "The default slot"(描述文本来自源码中 JSX 注释或默认约定,具体以你配置的文档生成行为为准);
  • 具名插槽:写<slot name="xxx" />,文档中会以`"xxx"`形式呈现(见 markdown-slots.ts 的处理逻辑);
  • 插槽转发:中间层组件不要吞掉子内容,而是把<slot />继续传给目标子组件,如上文slot-parent-cmp所示;
  • 验证:仿照 slot-cmp-container.e2e.ts 编写 e2e 断言,用shadowRoot.textContentpage.find校验最终渲染文本与顺序。

七、小结

slot-parent-cmp的自动文档虽小,却完整映射出一个 Stencil 插槽嵌套组件的全部关键事实:@Prop属性声明与默认值、默认插槽的声明与转发、组件间依赖关系与可视化依赖图、以及 e2e 对渲染顺序的验证。理解它,就等于理解了 Stencil 文档生成器(src/compiler/docs/readme)如何从源码元数据产出 API 文档,也掌握了编写"可转发插槽"组件时的标准姿势。后续可继续阅读 test/wdio 下 slot 系列目录(如 slot-nested-order、slot-forwarded-slot 相关用例)与 nested-slot-forwarding.spec.tsx,进一步探索更复杂的插槽投影边界情况。

  • 开发工具
  • 前端
  • 前端构建

【免费下载链接】stencil

A toolchain for building scalable, enterprise-ready component systems on top of TypeScript and Web Component standards. Stencil components can be distributed natively to React, Angular, Vue, (+ more) and traditional web applications from a single, framework-agnostic codebase.

项目地址:https://gitcode.com/gh_mirrors/st/stencil
点击查看免费下载

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询