代码片段 Snippet 与 @render
本教程共 50 篇 · 第 15 篇 · 更新于 2026-08-05 · 约 6 分钟阅读
15. 代码片段 Snippet 与 @render
本节目标:掌握 Snippet 的定义和渲染、参数传递、children 默认片段,以及如何将 snippet 作为 props 传递给组件。学完你能在模板中复用标记片段,替代 Svelte 4 的 slot。
什么是 Snippet
Snippet(代码片段)是 Svelte 5 中可复用的模板片段。你可以把它理解为模板里的函数——定义一次,多次调用。
先看一个简单例子:
{#snippet sum(a, b)}
<p>{a} + {b} = {a + b}</p>
{/snippet}
{@render sum(1, 2)}
{@render sum(3, 4)}
{@render sum(5, 6)}
输出:
1 + 2 = 3
3 + 4 = 7
5 + 6 = 11
{#snippet name()} 定义片段,{@render name()} 渲染它。就像定义函数和调用函数一样。
解决重复代码
Snippet 最直接的用途是消除重复标记。假设你有一组图片,有些带链接有些不带:
不使用 snippet(重复代码):
{#each images as image}
{#if image.href}
<a href={image.href}>
<figure>
<img src={image.src} alt={image.caption} />
<figcaption>{image.caption}</figcaption>
</figure>
</a>
{:else}
<figure>
<img src={image.src} alt={image.caption} />
<figcaption>{image.caption}</figcaption>
</figure>
{/if}
{/each}
使用 snippet(消除重复):
{#snippet figure(image)}
<figure>
<img src={image.src} alt={image.caption} />
<figcaption>{image.caption}</figcaption>
</figure>
{/snippet}
{#each images as image}
{#if image.href}
<a href={image.href}>
{@render figure(image)}
</a>
{:else}
{@render figure(image)}
{/if}
{/each}
Snippet 参数
Snippet 可以有任意多个参数,支持默认值和解构,但不支持 rest 参数(...args):
{#snippet greet(name, greeting = '你好')}
<p>{greeting},{name}!</p>
{/snippet}
{@render greet('码上学')}
{@render greet('Svelte', 'Hello')}
参数可以解构:
{#snippet userInfo({ name, age })}
<p>{name},{age}岁</p>
{/snippet}
{@render userInfo({ name: '码上学', age: 25 })}
Snippet 作用域
Snippet 可以引用外部变量(如 <script> 中声明的、{#each} 块中的),并且对同作用域的内容可见:
<script>
let { message = '很高兴见到你!' } = $props();
</script>
{#snippet hello(name)}
<p>你好 {name}!{message}</p>
{/snippet}
<!-- 同一作用域可以调用 -->
{@render hello('Alice')}
{@render hello('Bob')}
Snippet 也可以自我引用或互相引用(递归):
{#snippet blastoff()}
<span>🚀</span>
{/snippet}
{#snippet countdown(n)}
{#if n > 0}
<span>{n}...</span>
{@render countdown(n - 1)}
{:else}
{@render blastoff()}
{/if}
{/snippet}
{@render countdown(5)}
<!-- 输出: 5... 4... 3... 2... 1... 🚀 -->
默认 children 片段
组件标签之间的内容自动成为 children snippet:
<!--- file: App.svelte --->
<Button>点击我</Button>
<!--- file: Button.svelte --->
<script>
let { children } = $props();
</script>
<button>{@render children()}</button>
<!-- 渲染为 <button>点击我</button> -->
可选 children
如果 children 可能不存在,用可选链或条件渲染:
<!-- 方式一:可选链 -->
{@render children?.()}
<!-- 方式二:条件渲染 + 兜底内容 -->
{#if children}
{@render children()}
{:else}
<span>默认内容</span>
{/if}
将 Snippet 作为 Props 传递
隐式传递(推荐)
直接在组件标签内部声明 snippet,它们会自动成为组件的 props:
<!--- file: App.svelte --->
<script>
import Table from './Table.svelte';
const fruits = [
{ name: '苹果', qty: 5, price: 2 },
{ name: '香蕉', qty: 10, price: 1 }
];
</script>
<Table data={fruits}>
{#snippet header()}
<th>水果</th>
<th>数量</th>
<th>单价</th>
<th>合计</th>
{/snippet}
{#snippet row(d)}
<td>{d.name}</td>
<td>{d.qty}</td>
<td>{d.price}</td>
<td>{d.qty * d.price}</td>
{/snippet}
</Table>
<!--- file: Table.svelte --->
<script>
let { data, header, row } = $props();
</script>
<table>
{#if header}
<thead><tr>{@render header()}</tr></thead>
{/if}
<tbody>
{#each data as d}
<tr>{@render row(d)}</tr>
{/each}
</tbody>
</table>
显式传递
也可以像普通 props 一样显式传递:
{#snippet mySnippet()}
<p>内容</p>
{/snippet}
<MyComponent content={mySnippet} />
Note隐式传递更简洁。只有当 snippet 需要在多个组件间共享时,才考虑显式声明后传递。
TypeScript 类型标注
Snippet 类型从 svelte 导入,用 Snippet 接口标注:
<script lang="ts">
import type { Snippet } from 'svelte';
interface Props {
data: any[];
children: Snippet; // 无参数
row: Snippet<[any]>; // 接受一个参数
optional?: Snippet; // 可选 snippet
}
let { data, children, row, optional }: Props = $props();
</script>
Snippet 的泛型参数是元组类型,表示 snippet 的参数列表:
| 类型 | 含义 |
|---|---|
Snippet | 无参数 |
Snippet<[any]> | 一个参数 |
Snippet<[string, number]> | 两个参数:string 和 number |
用泛型让 data 和 row 类型关联:
<script lang="ts" generics="T">
import type { Snippet } from 'svelte';
let {
data,
row
}: {
data: T[];
row: Snippet<[T]>;
} = $props();
</script>
Snippet vs Svelte 4 Slot
| 对比项 | Svelte 5 Snippet | Svelte 4 Slot |
|---|---|---|
| 定义方式 | {#snippet name()} | <slot name="name"> |
| 渲染方式 | {@render name()} | <slot name="name" /> |
| 参数传递 | name(arg1, arg2) | let:arg |
| 默认内容 | children + ?: | <slot>默认</slot> |
| 可组合性 | 高(可递归、可互相引用) | 低 |
| 类型安全 | 有(Snippet 类型) | 无 |
TipSlot 在 Svelte 5 中已弃用。新项目全部用 Snippet。如果你在迁移 Svelte 4 代码,参照上表一一替换即可。
导出 Snippet
在 <script module> 中导出 snippet,可以在其他组件中复用:
<!--- file: snippets.svelte --->
<script module>
export { add };
</script>
{#snippet add(a, b)}
{a} + {b} = {a + b}
{/snippet}
<!-- 使用 -->
<script>
import { add } from './snippets.svelte';
</script>
{@render add(1, 2)}
Note导出的 snippet 不能引用非模块
<script>中的变量。这需要 Svelte 5.5.0 或更高版本。
本节回顾
{#snippet name(参数)}定义代码片段,{@render name(参数)}渲染- Snippet 像模板函数:可以有参数、默认值、解构,但不能用 rest 参数
- 可以引用外部变量,可以递归和互相引用
- 组件标签内的非 snippet 内容自动成为
childrenprop - 隐式传递:组件标签内声明
{#snippet}自动成为 props - TypeScript 用
Snippet接口标注,泛型参数是参数元组 - Snippet 替代了 Svelte 4 的 slot,更灵活、有类型安全