Компонент на 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
- пока ничего: введите слово или нажмите «Подробнее»
Проверьте себя: чем $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
Карточки
Эти карточки попадут в повторение, когда вы завершите урок.
Какие руны есть в 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: Introduction to runes только ссылка, по-английски
- Svelte Tutorial только ссылка, по-английски
- SvelteKit: Introduction только ссылка, по-английски
- MDN: Getting started with Svelte CC BY-SA, по-английски
Видео
Svelte 5 за 40 минут: руны (PurpleSchool, Антон Ларичев)
Видео загрузится с youtube-nocookie.com только после нажатия.
Дальше
Теперь вы знаете: компонент на Svelte.