Компонент на Svelte

Зачем:Компонент это кусок интерфейса со своими данными, разметкой и оформлением. После урока вы читаете файл .svelte, понимаете руны $state, $derived и $props и переносите блок страницы в компонент: карточку и список с поиском.

30 минут

Озвучено синтезом речи (Qwen3-TTS), голос не принадлежит человеку.

Компонент Svelte — файл .svelte с кодом, разметкой и стилями одного куска интерфейса. Руны $state, $derived и $props задают его состояние и входные данные.

Файл, в котором живёт кусок интерфейса

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

Компонент Svelte лежит в файле с расширением .svelte и состоит из трёх частей, которые вы уже знаете: код (<script>), разметка (обычный HTML с несколькими добавками) и оформление (<style>, необязательно). Вот карточка предложения целиком:

<script>
  let { item } = $props();
  let open = $state(false);
</script>

<article class="card">
  <h3>{item.title}</h3>
  <p class="price">{item.price}</p>
  <button type="button" aria-expanded={open} onclick={() => (open = !open)}>Подробнее</button>
  {#if open}
    <p>{item.text}</p>
  {/if}
</article>

Ничего необычного для человека, написавшего HTML. Необычны три вещи: слова со знаком доллара, фигурные скобки в разметке и то, что страница после нажатия кнопки меняется, хотя мы ни разу не написали «найди элемент и переключи». Разберём по порядку.

Руны: слова, которые понимает компилятор

Слова со знаком доллара называются рунами. Это особые слова Svelte 5: компилятор видит их и превращает обычный код в реактивный.

  • $props() принимает данные от родителя. Запись let { item } = $props() читается так: «мне передадут свойство item». Родитель пишет <ItemCard {item} />. Данные идут вниз, от родителя к потомку.
  • $state(значение) объявляет состояние: значение, изменение которого обновляет экран. let open = $state(false): пока open ложно, блок с описанием не показан. Нажали кнопку, написали open = !open, и Svelte сам показал блок. Заметьте: это обычное присваивание, никаких setOpen.
  • $derived(выражение) объявляет значение, которое вычисляется из других и пересчитывается само. Если у вас есть query (что введено в поиск) и список items, то let shown = $derived(items.filter(...)) будет всегда отвечать текущему запросу.
  • $effect(() => ...) запускает побочное действие после того, как состояние изменилось, например записать значение в localStorage. Берите его в последнюю очередь: большинство задач решаются тремя первыми рунами.

Вот список с поиском: он получает все пункты, хранит запрос и показывает отфильтрованное.

<script>
  import ItemCard from './ItemCard.svelte';

  let { items } = $props();
  let query = $state('');
  let shown = $derived(
    items.filter((item) => item.title.toLowerCase().includes(query.trim().toLowerCase()))
  );
</script>

<label>Найти в списке <input type="search" bind:value={query}></label>
<p role="status">Показано: {shown.length} из {items.length}</p>

<div class="cards">
  {#each shown as item (item.title)}
    <ItemCard {item} />
  {:else}
    <p>Ничего не нашлось.</p>
  {/each}
</div>

Сравните с уроком про события: там вы вручную ловили input, перебирали карточки, писали card.hidden и отдельно обновляли счётчик. Здесь счётчик, список и пустой экран выведены из двух значений: items и query. Забыть обновить что-то нельзя.

Фигурные скобки и блоки

В разметке фигурные скобки значат «здесь JavaScript»:

  • {выражение} вставляет значение. Это текст, а не разметка: теги в строке остаются буквами, как у textContent. Поэтому {item.title} безопасен для чужих данных. Опасна только запись {@html строка}, которая вставляет разметку, как innerHTML: ей пользуются лишь для кода, написанного целиком вами;
  • {#if условие} ... {:else} ... {/if} показывает блок по условию;
  • {#each список as элемент (ключ)} ... {:else} ... {/each} повторяет блок для каждого элемента списка. Ключ в скобках помогает Svelte понять, какая карточка какому пункту принадлежит, когда список фильтруют или переставляют, и не потерять состояние (например, раскрытость «Подробнее»). Ветка {:else} показывается на пустой список: готовое «ничего не нашлось»;
  • onclick={функция} вешает обработчик, а bind:value={query} связывает поле с состоянием в обе стороны: набрали в поле, и query изменился; поменяли query, и поле показывает новое.

Ещё одна вещь, которую легко не заметить: стили в <style> компонента по умолчанию относятся только к нему. Карточка не испортит оформление остальной страницы, а правило, к которому ничего не подходит, из результата выбросят.

Что на самом деле происходит

Браузер не знает, что такое Svelte. Во время сборки компилятор превращает каждый .svelte в обычный JavaScript: для каждого места, где {item.title} зависит от состояния, он пишет короткую команду «если это значение изменилось, поменять вот этот текстовый узел». Поэтому в браузер почти не едет библиотека-посредник и нет сравнения «старого и нового» дерева, как в React: обновляется ровно то, что зависит от изменившегося.

Проверьте это сами. Справа живой компонент на тех же рунах, а под ним журнал: что именно в дереве страницы поменялось после каждого вашего действия.

Компонент и то, что в нём обновилось
Код
let query = $state('');
let shown = $derived(
  items.filter((i) => i.title.includes(query))
);

<input bind:value={query}>
<p>Показано: {shown.length}</p>
{#each shown as item (item.title)}
  <article>…</article>
{/each}
Живой результат

Показано: 3 из 3

Первое предложение
Второе предложение
Третье предложение
Что изменилось в дереве страницы: всего 0
  1. пока ничего: введите слово или нажмите «Подробнее»
Проверьте себя: чем $derived отличается от $state?

$state хранит значение, которое вы меняете сами. $derived вычисляется из других значений и пересчитывается автоматически, присваивать его вручную не нужно.

Как запустить у себя

В нашей песочнице компоненты не собираются: для этого нужен компилятор. Есть два простых способа попробовать.

  • Откройте svelte.dev/playground, вставьте в него файлы App.svelte, ItemList.svelte и ItemCard.svelte из практики, и вы увидите страницу сразу.
  • Создайте проект командой npx sv create моё-приложение (понадобится Node.js). Она поставит SvelteKit, то есть Svelte вместе с маршрутами по папкам, подготовкой страниц на сервере и сборщиком Vite. Дальше npm install и npm run dev.

Различие, о котором стоит знать заранее: в простом проекте на одном Svelte и Vite страница пуста, пока не загрузится скрипт. В SvelteKit же страница отрисовывается на сервере и приходит в браузер готовым HTML, а скрипт потом её «оживляет». Для посетителя без скрипта разница огромная, и об этом следующий урок.

Практика

Вы на сайте эпохи 2019 года. Перенесите блок предложений в компоненты: карточку ItemCard, список с поиском ItemList и точку сборки App. Код пишется текстом в полях блока «Компоненты страницы»: внутри них можно использовать знак < как есть.

Проверки читают текст ваших компонентов: есть ли $props, $state, $derived, ключ в {#each} и ветка {:else}. Работают ли они, лучше проверить в песочнице Svelte. Заодно честно посчитайте, сколько строк кода понадобилось вручную (ваши четыре скрипта на странице) и в компонентах, и напишите, что увидит посетитель, у которого JavaScript не загрузился. Это очень полезная мысль для следующего урока.

Что было не так раньше

До пятой версии Svelte писали иначе: состояние объявлялось обычным let, а реактивность подразумевалась (спецсимвол $: для вычисляемых значений и особые правила присваивания). Это было коротко, но магия иногда подводила: она работала только внутри компонента и не переходила в обычные файлы, а новичку было трудно понять, почему присваивание в одном месте реактивно, а в другом нет. В 2024 году вышел Svelte 5 с рунами: те же возможности, но запись явная.

Идея, что вид выводится из состояния, а изменения распространяются по цепочке зависимостей (её называют сигналами), в 2020-е годы пришла почти во все фреймворки: в Solid, Preact, Angular, в Vue (там это ref). Выучив её на Svelte, вы легко прочтёте остальные.

Практика

Готовлю вашу страницу…

Тест

Вопрос 1 из 6

Что делает строка let count = $state(0) в компоненте Svelte 5?

Один ответ

Карточки

Эти карточки попадут в повторение, когда вы завершите урок.

  • Какие руны есть в Svelte 5 и что они делают?

    Ответ

    $state: реактивное значение. $derived: значение, вычисляемое из других. $props: данные от родителя. $effect: побочное действие после изменения. Это слова, которые понимает компилятор.

  • Как компоненты обмениваются данными?

    Ответ

    Родитель передаёт данные потомку через пропсы (<Card {item} />), потомок берёт их через $props(). Данные идут вниз, а события обработчиками вверх.

  • Как вывести список в Svelte и зачем ключ?

    Ответ

    {#each items as item (item.id)} … {:else} пусто {/each}. Ключ в скобках помогает Svelte сопоставить элементы с пунктами при фильтре и перестановке.

  • Чем Svelte отличается от React и Vue?

    Ответ

    Это компилятор: на сборке он превращает компонент в обычный JavaScript, который меняет нужные узлы напрямую; в браузер не едет тяжёлая библиотека и нет виртуального DOM.

  • Что такое SvelteKit?

    Ответ

    Мета-фреймворк над Svelte: маршруты по папкам, серверная отрисовка и подготовка страниц заранее, сборка на Vite. Этот учебник построен на нём.

Источники

Видео

Svelte 5 за 40 минут: руны (PurpleSchool, Антон Ларичев)

Видео загрузится с youtube-nocookie.com только после нажатия.

Открыть на сайте

Дальше

Теперь вы знаете: компонент на Svelte.