Логика шаблонов: if, each и await

Блоки {#if}, {#each} и {#await} добавляют логику прямо в разметку — условия, циклы и асинхронность.

«Разметка должна уметь думать.» Логические блоки Svelte позволяют выражать условия и циклы декларативно, не покидая шаблон.

Интерфейс редко статичен: то нужно показать блок при условии, то отрисовать список, то дождаться данных. В Svelte для этого есть логические блоки — особые конструкции в фигурных скобках с решёткой. Они читаются почти как обычный псевдокод и компилируются в эффективные адресные обновления.

Условие — это {#if}...{:else if}...{:else}...{/if}. Цикл — {#each items as item}...{/each}, причём крайне желательно указывать ключ: {#each items as item (item.id)}, чтобы Svelte точечно обновлял именно изменившиеся строки, а не пересоздавал список. Асинхронность — {#await promise}, который умеет показывать состояние загрузки, успех и ошибку.

<script>
  let items = $state([{ id: 1, text: 'Молоко' }, { id: 2, text: 'Хлеб' }]);
  let show = $state(true);
</script>

{#if show}
  <ul>
    {#each items as item (item.id)}
      <li>{item.text}</li>
    {:else}
      <li>Список пуст</li>
    {/each}
  </ul>
{:else}
  <p>Список скрыт</p>
{/if}

Блок {:else} внутри {#each} срабатывает, когда массив пуст — удобный способ показать заглушку. А {#await} особенно элегантен для запросов:

{#await fetchUser()}
  <p>Загрузка...</p>
{:then user}
  <p>Привет, {user.name}</p>
{:catch error}
  <p>Ошибка: {error.message}</p>
{/await}

Как это работает под капотом

Компилятор превращает каждый блок в управляющий код, который создаёт и удаляет узлы по условию или перебирает массив. Для {#each} с ключом Svelte ведёт сопоставление «ключ → узел» и при изменении массива переиспользует существующие узлы. Смоделируем выигрыш от ключей.

// Почему ключи в #each важны: переиспользование вместо пересоздания
let domNodes = new Map(); // ключ -> 'узел'
function render(items) {
  let created = 0, reused = 0;
  const next = new Map();
  for (const it of items) {
    if (domNodes.has(it.id)) { reused++; next.set(it.id, domNodes.get(it.id)); }
    else { created++; next.set(it.id, 'node#' + it.id); }
  }
  domNodes = next;
  console.log('создано:', created, 'переиспользовано:', reused);
}

render([{id:1},{id:2}]);            // создано: 2 переиспользовано: 0
render([{id:2},{id:1},{id:3}]);     // создано: 1 переиспользовано: 2 (порядок сменился!)

Попробуй сам ▶ — вставь код в консоль браузера (F12 → Console) и нажми Enter, чтобы увидеть вывод.

  массив items
     |
     v
  {#each items as i (i.id)}
     |
     +-- ключ найден  -> переиспользовать узел
     +-- ключ новый   -> создать узел
     +-- ключ исчез   -> удалить узел

Частые ошибки

  • Не указывать ключ в {#each}. Без него обновления списка медленнее и возможны баги с состоянием строк.
  • Брать индекс как ключ при перестановках. Лучше стабильный идентификатор элемента.
  • Городить вычисления в разметке. Сложную логику выносите в $derived.

Best practices

  • Всегда задавайте стабильный ключ в циклах по данным с идентификаторами.
  • Используйте {#await} вместо ручных флагов загрузки — он чище и безопаснее.
  • Держите разметку декларативной; тяжёлую логику считайте заранее в скрипте.

Декларативная разметка против ручного DOM

Чтобы оценить логические блоки, вспомните, как условный рендеринг делали раньше — вручную: создавали элемент, проверяли условие, добавляли или удаляли узел из DOM, не забывали почистить за собой при обратном изменении. Это был источник бесконечных багов: забытое удаление, рассинхрон между состоянием и тем, что на экране, утечки обработчиков. Логические блоки Svelte превращают всё это в декларацию: вы просто пишете «если условие — покажи это, иначе — то», а движок берёт на себя создание и удаление узлов в нужный момент. То же самое с циклами и асинхронностью. Вы описываете желаемый результат для каждого состояния данных, а не последовательность шагов по его достижению. Особенно ярко это видно в блоке {#await}: три состояния асинхронной операции — загрузка, успех, ошибка — описываются рядом, наглядно, без ручных флагов и без риска показать устаревшие данные. Декларативность здесь не просто красиво — она устраняет целый класс ошибок.

Итог: логические блоки {#if}, {#each} и {#await} вносят условия, циклы и асинхронность прямо в разметку. Ключи в циклах включают точечные обновления и предотвращают баги.

Проверьте себя
1. Зачем указывать ключ в {#each items as item (item.id)}?
AЧтобы отсортировать список
BЧтобы Svelte точечно обновлял и переиспользовал именно изменившиеся строки
CЧтобы скрыть пустые элементы
DЭто обязательно для компиляции
2. Какой блок удобно использовать для отображения состояний загрузки, успеха и ошибки промиса?
A{#if}
B{#each}
C{#await}...{:then}...{:catch}
D{#key}