{#snippet ...}
{#snippet name()}...{/snippet}
{#snippet name(param1, param2, paramN)}...{/snippet}
Фрагменты и теги render — это способ создавать многократно используемые блоки разметки внутри ваших компонентов. Вместо того чтобы писать повторяющийся код, как здесь…
{#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}
Как и функции, фрагменты могут принимать любое количество параметров, у которых могут быть значения по умолчанию, и вы можете деструктурировать эти параметры. Однако вы не можете использовать остаточные параметры (rest).
Область видимости фрагментов
Фрагменты могут быть объявлены в любом месте внутри вашего компонента. Они могут ссылаться на значения, объявленные вне их самих, например, в теге <script>
или в блоках {#each ...}
(демонстрация)…
<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()}
Фрагменты могут ссылаться на себя и друг на друга (демонстрация):
{#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)}
Передача фрагментов в компоненты
Внутри шаблона фрагменты являются значениями, как и любые другие. Следовательно, их можно передавать в компоненты как пропсы (демонстрация):
<script>
import Table from './Table.svelte';
const fruits = [
{ name: 'apples', qty: 5, price: 2 },
{ name: 'bananas', qty: 10, price: 1 },
{ name: 'cherries', qty: 20, price: 0.5 }
];
</script>
{#snippet header()}
<th>fruit</th>
<th>qty</th>
<th>price</th>
<th>total</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} />
Думайте об этом как о передаче содержимого, а не данных, в компонент. Концепция похожа на слоты в веб-компонентах.
Для удобства написания кода фрагменты, определённые внутри компонента, автоматически обрабатываются как пропсы этого компонента (демонстрация):
<!-- это семантически то же самое, что и выше -->
<Table data={fruits}>
{#snippet header()}
<th>fruit</th>
<th>qty</th>
<th>price</th>
<th>total</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
(демонстрация):
// App.svelte
<Button>нажми меня</Button>
// Button.svelte
<script>
let { children } = $props();
</script>
<!-- Результатом будет <button>нажми меня</button> -->
<button>{@render children()}</button>
// Button.svelte
<script lang="ts">
let { children } = $props();
</script>
<!-- Результатом будет <button>нажми меня</button> -->
<button>{@render children()}</button>
Вы можете объявить пропсы фрагментов как необязательные. Используйте опциональную цепочку, чтобы не отображать ничего, если фрагмент не задан…
<script>
let { children } = $props();
</script>
{@render children?.()}
…или используйте блок #if
, чтобы отобразить альтернативный контент:
<script>
let { children } = $props();
</script>
{#if children}
{@render children()}
{:else}
альтернативный контент
{/if}
Типизация фрагментов
Фрагменты реализуют интерфейс Snippet
, импортируемый из 'svelte'
:
<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
и фрагмента 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>
(непосредственно или косвенно через другие фрагменты) (демонстрация):
<script module>
export { add };
</script>
{#snippet add(a, b)}
{a} + {b} = {a + b}
{/snippet}
Программные фрагменты
Фрагменты могут быть созданы программно с помощью API createRawSnippet
. Это предназначено для продвинутых случаев использования.
Фрагменты и слоты
В Svelte 4 контент можно передавать в компоненты с использованием слотов. Фрагменты более мощные и гибкие, поэтому слоты устарели в Svelte 5.