Svelte 遗留模式中的 `<slot>`:从默认插槽、命名插槽到 let: 数据回传的完整实现解析
在 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>时,内容会作为childrenprop(snippet 形式)传入,同时标记$$slots.default: true——这样即使子组件是 Svelte 5 新写法(使用<svelte:render this={children} />)也能正确渲染,而旧写法的<slot>同样可用; - 当默认插槽带有
let:指令(需要回传数据)时,内容只能走$$slots.default(因为childrensnippet 通道不携带 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 行为。
小结:关键实现路径速查
- 插槽归组:utils/slot.js 的
determine_slot读取slot文本属性决定分组名; - 父组件序列化:client/visitors/shared/component.js 生成各 slot 渲染函数,注入
$$slots与childrenprop(含$.invalid_default_snippet的防误用保护); - 子组件渲染:client/visitors/SlotElement.js 把
<slot name="...">编译为$.slot($$props, name, props, fallback),fallback 即<slot>内部片段; - 服务端对等实现:server/visitors/SlotElement.js,保证 SSR 输出与客户端一致;
- 相关遗留 API:
$$slots的自省(21-legacy-$$slots.md)、<svelte:fragment>的let:与slot用法(22-legacy-svelte-fragment.md)。
对仍在 legacy 模式中的项目,理解"slot 属性负责分组、$$slots 对象负责传递、let: 负责数据回传、children prop 负责新旧 API 桥接"这条主线,就能准确预测每个写法在编译后的真实行为,也为后续向 Svelte 5 snippets 语法迁移打下基础。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0627
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00