{#snippet ...}
{#snippet name()}...{/snippet}
{#snippet name(param1, param2, paramN)}...{/snippet}
代码片段和 渲染标签 是在组件内部创建可复用标记块的一种方法。与其编写这样的重复代码...
{#each images as image}
{#if image.href}
<a href={image.href}>
<figure>
<img src={image.src} alt={image.caption} width={image.width} height={image.height} />
<figcaption>{image.caption}</figcaption>
</figure>
</a>
{:else}
<figure>
<img src={image.src} alt={image.caption} width={image.width} height={image.height} />
<figcaption>{image.caption}</figcaption>
</figure>
{/if}
{/each}
...你可以这样写:
{#snippet figure(image)}
<figure>
<img src={image.src} alt={image.caption} width={image.width} height={image.height} />
<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}
像函数声明一样,代码片段可以有任意数量的参数,这些参数可以有默认值,并且你可以对每个参数进行解构。然而,你不能使用剩余参数。
代码片段作用域
代码片段可以在组件的任何地方声明。它们可以引用在自身之外声明的值,例如在 <script>
标签或 {#each ...}
块中 (demo)...
<script>
let { message = `很高兴见到你!` } = $props();
</script>
{#snippet hello(name)}
<p>你好 {name}! {message}!</p>
{/snippet}
{@render hello('alice')}
{@render hello('bob')}
...并且它们对同一词法作用域中的所有内容都是”可见的”(即兄弟节点和这些兄弟节点的子节点):
<div>
{#snippet x()}
{#snippet y()}...{/snippet}
<!-- 这很好 -->
{@render y()}
{/snippet}
<!-- 这将出错,因为 `y` 不在作用域中 -->
{@render y()}
</div>
<!-- 这也将出错,因为 `x` 不在作用域中 -->
{@render x()}
代码片段可以引用自身和其他片段 (demo):
{#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(10)}
将代码片段传递给组件
在模板中,代码片段和其他值一样。这样,它们可以作为 props 传递给组件 (demo):
<script>
import Table from './Table.svelte';
const fruits = [
{ name: '苹果', qty: 5, price: 2 },
{ name: '香蕉', qty: 10, price: 1 },
{ name: '樱桃', qty: 20, price: 0.5 }
];
</script>
{#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 data={fruits} {header} {row} />
把它想象成向组件传递内容而非数据。这个概念类似于 Web 组件中的插槽。
为了方便,直接在组件内部声明的代码片段会隐式成为组件的 props (demo):
<!-- 这在语义上与上面的相同 -->
<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>
组件标签内的任何不是代码片段声明的内容都将隐式成为 children
代码片段的一部分 (demo):
<Button>点击我</Button>
<script>
let { children } = $props();
</script>
<!-- 结果将是 <button>点击我</button> -->
<button>{@render children()}</button>
<script lang="ts">
let { children } = $props();
</script>
<!-- 结果将是 <button>点击我</button> -->
<button>{@render children()}</button>
请注意,如果组件内部还有内容,你不能有名为
children
的 prop — 基于这个原因,应该避免使用这个名称作为 prop
你可以将代码片段 props 声明为可选的。你可以使用可选链,当代码片段未设置时不渲染任何内容...
<script>
let { children } = $props();
</script>
{@render children?.()}
...或者使用 #if
块来渲染后备内容:
<script>
let { children } = $props();
</script>
{#if children}
{@render children()}
{:else}
后备内容
{/if}
代码片段类型
代码片段实现了从 'svelte'
导入的 Snippet
接口:
<script lang="ts">
import type { Snippet } from 'svelte';
interface Props {
data: any[];
children: Snippet;
row: Snippet<[any]>;
}
let { data, children, row }: Props = $props();
</script>
通过这项改动,如果你尝试在没有提供 data
prop 和 row
代码片段的情况下使用该组件,则会出现红色波浪线。请注意,提供给 Snippet
的类型参数是一个元组,因为代码片段可以有多个参数。
我们可以通过声明泛型来进一步收窄类型,以便 data
和 row
引用相同的类型:
<script lang="ts" generics="T">
import type { Snippet } from 'svelte';
let {
data,
children,
row
}: {
data: T[];
children: Snippet;
row: Snippet<[T]>;
} = $props();
</script>
导出代码片段
在 .svelte
文件顶层声明的代码片段可以从 <script module>
导出以供其他组件使用,前提是它们不引用非模块 <script>
中的任何声明(无论是直接引用还是通过其他代码片段间接引用) (demo):
<script module>
export { add };
</script>
{#snippet add(a, b)}
{a} + {b} = {a + b}
{/snippet}
这需要 Svelte 5.5.0 或更新版本
程序化代码片段
代码片段可以通过 createRawSnippet
API 以编程方式创建。这适用于高级用例。
代码片段和插槽
在 Svelte 4 中,可以使用 插槽 将内容传递给组件。代码片段更强大、更灵活,因此在 Svelte 5 中插槽已被弃用。