Перейти к содержимому
GitHub

{#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>

Вы можете объявить пропсы фрагментов как необязательные. Используйте опциональную цепочку, чтобы не отображать ничего, если фрагмент не задан…

<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.