Snippets 与 Slots
在 Svelte 5 中,Snippets(片段) 取代了旧的 <slot> 机制,成为向组件传递模板内容的标准方式。Snippets 更强大:可以带参数、可以被多次渲染、可以像值一样传递。
从 Slots 说起
Svelte 4 用 <slot> 接收父组件传入的内容:
svelte
<!-- Svelte 4(已过时) -->
<!-- Card.svelte -->
<div class="card">
<slot />
</div>Svelte 5 用 {@render children()} 来渲染默认内容,children 是一个特殊的 prop:
svelte
<!-- Svelte 5 -->
<!-- Card.svelte -->
<script>
let { children } = $props();
</script>
<div class="card">
{@render children()}
</div>使用默认 Snippet
父组件把标签内的内容作为默认 snippet 传入:
svelte
<script>
import Card from './Card.svelte';
</script>
<Card>
<p>这些内容会渲染到 Card 内部</p>
</Card>具名 Snippets
用 {#snippet 名字()} 定义具名片段,作为同名 prop 传给子组件:
svelte
<!-- Card.svelte -->
<script>
let { children, header, footer } = $props();
</script>
<div class="card">
{#if header}
<div class="header">{@render header()}</div>
{/if}
<div class="body">{@render children()}</div>
{#if footer}
<div class="footer">{@render footer()}</div>
{/if}
</div>svelte
<!-- 父组件 -->
<Card>
{#snippet header()}
<h3>卡片标题</h3>
{/snippet}
<p>这是主体内容(默认 snippet)</p>
{#snippet footer()}
<small>页脚信息</small>
{/snippet}
</Card>带参数的 Snippets
Snippets 最强大的地方:可以接收参数。这让它们能渲染动态内容:
svelte
<!-- List.svelte -->
<script>
let { items, row } = $props();
</script>
<ul>
{#each items as item, i (i)}
<!-- 把每个 item 传给父组件定义的 row snippet -->
<li>{@render row(item, i)}</li>
{/each}
</ul>svelte
<!-- 父组件 -->
<script>
import List from './List.svelte';
let users = $state([
{ name: '小明', age: 20 },
{ name: '小红', age: 22 }
]);
</script>
<List items={users}>
{#snippet row(user, index)}
<strong>{index + 1}.</strong> {user.name}({user.age} 岁)
{/snippet}
</List>这里 List 负责遍历,但每一行长什么样由父组件决定 —— 这是一种非常灵活的控制反转模式(类似 React 的 render props)。
Snippets 是值
Snippets 可以存进变量、作为 props 传递、甚至放进 $state:
svelte
<script>
let { content } = $props();
// content 是一个 snippet 值,可以在多处渲染
</script>
<div>{@render content()}</div>
<hr />
<div>{@render content()}</div>为什么 Snippets 比 Slots 好
| 能力 | <slot>(Svelte 4) | Snippets(Svelte 5) |
|---|---|---|
| 带参数渲染 | ❌ | ✅ {@render row(item)} |
| 多次渲染同一片段 | 有限 | ✅ |
| 作为值传递/存储 | ❌ | ✅ |
| 条件渲染片段 | 别扭 | ✅ {#if header} |
| 类型检查 | 弱 | 强(配合 TS) |
试一试
下面的卡片组件综合演示了具名 snippets(header / footer)和默认 snippet(children),并且 footer 里能读到父组件的状态。点点赞试试:
Snippets · {@render}Svelte 5 · Live
对应源码(子组件 Card.svelte):
svelte
<script>
let { children, header, footer } = $props();
</script>
<div class="card">
{#if header}<div class="card__header">{@render header()}</div>{/if}
<div class="card__body">{@render children()}</div>
{#if footer}<div class="card__footer">{@render footer()}</div>{/if}
</div>父组件传入三个 snippets:
svelte
<Card>
{#snippet header()}📦 Svelte 5 Snippets{/snippet}
<p>这是 children 默认片段</p>
<button onclick={() => likes++}>👍 点赞 {likes}</button>
{#snippet footer()}当前点赞 {likes}{/snippet}
</Card>小结
| 要点 | 说明 |
|---|---|
{@render children()} | 渲染默认内容(取代 <slot>) |
{#snippet name()} | 定义具名片段 |
{@render row(item)} | 渲染带参数的片段 |
| 片段即值 | 可传递、存储、多次渲染 |
| 控制反转 | 子组件控制何时/如何渲染父组件提供的模板 |
下一步:事件处理。