首页
/ Svelte 遗留模式中的 `<slot>`:从默认插槽、命名插槽到 let: 数据回传的完整实现解析

Svelte 遗留模式中的 `<slot>`:从默认插槽、命名插槽到 let: 数据回传的完整实现解析

2026-09-06 13:56:26作者:廉彬冶Miranda

在 Svelte 5 中,向组件传递内容的主流方式是 snippets 配合 <svelte:render> 渲染;但在 legacy 模式下,组件标签内部的内容仍被视为 slotted content,由子组件通过 <slot> 元素渲染。本文以 Svelte 仓库中 legacy API 章节的 <slot> 文档为主体,完整梳理默认插槽、命名插槽、fallback 内容与通过 let: 向插槽内容回传数据的用法,并结合编译器源码(客户端/服务端 transform 阶段)解析 <slot>slot="..." 属性最终如何被编译为 $$slots prop 与 $.slot 运行时调用,帮助你在迁移和混用新、旧 API 时建立可靠的实现级认知。

背景:Svelte 5 的 snippets 与 legacy 插槽并存

Svelte 5 引入了 snippets 作为内容传递机制,配合 render tags 在组件内渲染。而 legacy 模式(即在组件中未使用 runes 语法时的兼容模式)保留了旧版 Svelte 的 <slot> 写法:组件标签内的内容被视为 slotted content,由组件内部一个 <slot> 元素在指定位置渲染。

这一兼容并非只是语法层面的:编译器会把父组件中的插槽内容序列化后传给子组件,使得新旧两种写法可以在同一项目内共存。仓库中 legacy API 文档(99-legacy 总目录)把 <slot>$$slots 属性svelte:fragment 等作为一组关联的遗留能力进行维护。

默认插槽:最基础的 slotted content

最典型的用法是:父组件在标签内写内容,子组件用 <slot> 指定落位。

<!-- file: App.svelte -->
<script>
	import Modal from './Modal.svelte';
</script>

<Modal>This is some slotted content</Modal>
<!-- file: Modal.svelte -->
<div class="modal">
	<slot></slot>
</div>

从源码结构看,父组件这一侧的转换发生在 client/visitors/shared/component.js 中:编译器遍历组件标签的直接子节点,调用 determine_slot(child) ?? 'default' 把每个子节点按其 slot 属性归组;对于没有 slot 属性的节点,一律归入 'default' 组。determine_slot 的实现位于 utils/slot.js,它只做一件事——检查元素节点上是否存在文本形式的 slot 属性,有则返回其值,无则返回 null

随后,default 组的内容会被编译为一个 (anchor, slotProps) => { ... } 形式的渲染函数,并通过 props 传递给子组件。

提示:如果你想在模板中渲染一个真实的 <slot> HTML 元素(而非 Svelte 的插槽占位),可以使用 <svelte:element this={'slot'} />,避免与 Svelte 的 <slot> 语法冲突。

命名插槽:用 slot 属性把内容分到不同位置

组件除了默认插槽,还可以拥有多个 named slots。父组件侧的用法是:在直接位于组件标签内的元素、组件或 <svelte:fragment> 上添加 slot="..." 属性。

<!-- file: App.svelte -->
<script>
	import Modal from './Modal.svelte';

	let open = true;
</script>

{#if open}
	<Modal>
		This is some slotted content

		<div slot="buttons">
			<button on:click={() => open = false}>
				close
			</button>
		</div>
	</Modal>
{/if}

子组件侧则添加对应的 <slot name="..."> 元素:

<!-- file: Modal.svelte -->
<div class="modal">
	<slot></slot>
	<hr>
	<slot name="buttons"></slot>
</div>

源码层面,前面提到的归组逻辑(determine_slot)正是命名插槽的核心:每个带 slot="buttons" 的子节点被放入 children['buttons'] 组。组件转换结束后,编译器为每个非空 slot 组生成一个渲染函数,并汇总进一个 $$slots 对象 prop:

// 概念上等价于(编译器生成代码的形态)
Modal(anchor, {
	...props,
	$$slots: {
		default: (anchor, slotProps) => { /* 默认插槽内容 */ },
		buttons: (anchor, slotProps) => { /* slot="buttons" 的内容 */ }
	}
});

注意编译器对 default 插槽有一个特殊的"双向兼容"处理(见 component.js):

  • 当默认插槽没有 let: 指令且没有携带 let:<svelte:fragment> 时,内容会作为 children prop(snippet 形式)传入,同时标记 $$slots.default: true——这样即使子组件是 Svelte 5 新写法(使用 <svelte:render this={children} />)也能正确渲染,而旧写法的 <slot> 同样可用;
  • 当默认插槽带有 let: 指令(需要回传数据)时,内容只能走 $$slots.default(因为 children snippet 通道不携带 slot props),同时传入一个 $.invalid_default_snippet 作为 children,防止子组件误把 legacy 内容当成无参数 snippet 使用。

Fallback content:插槽为空时的兜底渲染

如果调用方没有提供任何 slotted content,组件可以在 <slot> 元素内部定义 fallback 内容:

<slot>
	This will be rendered if no slotted content is provided
</slot>

从编译产物看,fallback 就是 $.slot 调用的最后一个参数:client/visitors/SlotElement.js 中,若 <slot> 片段为空,fallback 传 null;否则把整个片段编译成一个 (anchor) => { ... } 的箭头函数。运行时若 $$slots 中不存在对应名称的渲染函数,就会执行这个 fallback 函数。服务端 SSR 走的是 server/visitors/SlotElement.js,逻辑一致——同样把 fallback 片段编译为 thunk 传给 $.slot,保证浏览器与服务器输出一致。

向插槽内容回传数据:slot props 与 let: 指令

插槽可以被渲染零次或多次(典型场景是循环内),并且可以通过 props 把值 回传 给父组件。父组件使用 let: 指令把子组件暴露的值绑定到插槽模板中。

默认插槽中的回传

<!-- file: FancyList.svelte -->
<ul>
	{#each items as data}
		<li class="fancy">
			<!-- 'item' here... -->
			<slot item={process(data)} />
		</li>
	{/each}
</ul>
<!-- file: App.svelte -->
<!-- ...corresponds to 'item' here: -->
<FancyList {items} let:item={processed}>
	<div>{processed.text}</div>
</FancyList>

let: 指令遵循常规的简写规则——let:item 等价于 let:item={item}<slot {item}> 等价于 <slot item={item}>

编译链路上,父组件的 let:item 会生成一段 lets 绑定代码;由于 default 插槽携带 let: 指令,父组件不会把内容走 children snippet 通道,而是按上文所述放入 $$slots.default。子组件侧,<slot item={...} /> 中的 item 属性被收集进 props 对象(状态依赖的属性会包装为 getter 以保持响应性),最终生成形如 $.slot($$props, 'default', { item: ... }, fallback) 的调用,$$slotProps 里就带着 item,父组件的 let: 绑定从该处取值。

命名插槽中的回传

命名插槽同样可以暴露值,此时 let: 指令要写在带 slot 属性的那个元素上(而不是 <svelte:fragment> 或组件标签上):

<!-- file: FancyList.svelte -->
<ul>
	{#each items as item}
		<li class="fancy">
			<slot name="item" item={process(data)} />
		</li>
	{/each}
</ul>

<slot name="footer" />
<!-- file: App.svelte -->
<FancyList {items}>
	<div slot="item" let:item>{item.text}</div>
	<p slot="footer">Copyright (c) 2019 Svelte Industries</p>
</FancyList>

这里有一个源码中值得注意的细节:在 component.js 中,若一个元素/组件本身带有 slot 属性(slot_scope_applies_to_itself),其上的 let: 指令会被绑定到该元素自身的作用域——即 let: 的作用范围不只是元素内部,元素自身也能直接引用绑定值。同时,带 slot 属性元素的 let: 会强制该组内容走 $$slots 通道而非 children snippet 通道,与默认插槽带 let: 时的处理保持同一套互斥逻辑。

另外,Svelte 5 风格的 SnippetBlock 作为子组件直接子节点时也会被转换:它作为普通 snippet prop 传入,同时编译器会写入 $$slots.<name> = true 标记(children 名称会归一为 default),使仍在使用 <slot> 的子组件也能接住这类内容——这是仓库为新旧 API 混用特意保留的 interop 行为。

小结:关键实现路径速查

对仍在 legacy 模式中的项目,理解"slot 属性负责分组、$$slots 对象负责传递、let: 负责数据回传、children prop 负责新旧 API 桥接"这条主线,就能准确预测每个写法在编译后的真实行为,也为后续向 Svelte 5 snippets 语法迁移打下基础。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.13 K
2.75 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
857
1.35 K
docsdocs
暂无描述
Markdown
897
5.8 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
529
593
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
916
1.83 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.58 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.35 K
1.46 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.01 K
515
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
547
388