Пособие по Svelte - фреймворку для создания UI
Это подробное пособие по Svelte — современному фреймворку для создания пользовательских интерфейсов. В отличие от React или Vue, Svelte работает на этапе компиляции, перенося основную работу из браузера в процесс сборки. Это даёт более быстрый старт и меньший размер бандла.
1. Введение
Svelte — это компилируемый фреймворк. Вы пишете компоненты на языке, похожем на HTML + JavaScript, а Svelte превращает их в оптимизированный чистый JavaScript.
Реактивность достигается не через виртуальный DOM, а через тонкие обновления реального DOM, когда данные меняются.
Основные преимущества:
- Минимальный код (меньше бойлерплейта)
- Отсутствие виртуального DOM → быстрый рендеринг
- Реактивность «из коробки»
- Встроенные переходы и анимации
- Настоящие CSS-стили на компонент (scoped)
2. Быстрый старт
2.1. Создание проекта
Самый простой способ — использовать шаблон sveltejs/template через degit:
npx degit sveltejs/template my-app cd my-app npm install npm run dev
Или через Vite (рекомендуется для новых проектов):
npm create vite@latest my-app -- --template svelte cd my-app npm install npm run dev
После запуска откройте http://localhost:5173.
2.2. Структура компонента
Компонент Svelte — это один .svelte файл, содержащий три секции:
<script> // логика (JavaScript) </script> <style> /* стили (CSS) */ </style> <!-- разметка (HTML + специальные теги) -->
Все три части опциональны.
3. Основы синтаксиса
3.1. Вывод данных
Используйте фигурные скобки { } для вывода значений:
<script>
let name = 'Мир';
</script>
<h1>Привет, {name}!</h1>
Внутри {} можно писать любые выражения:
<p>{name.toUpperCase()} – {10 + 5}</p>
3.2. Атрибуты
Для динамических атрибутов используйте те же фигурные скобки:
<script>
let src = 'image.png';
let disabled = true;
</script>
<img {src} alt="Описание"> <!-- сокращённая запись src={src} -->
<button disabled={disabled}>Кнопка</button>
3.3. Стилизация компонента
Стили внутри <style> автоматически изолируются (scoped). Классы генерируются уникальными.
<style>
p {
color: blue;
}
</style>
<p>Этот текст синий</p>
<p class="other">А этот – нет, потому что нет глобального класса</p>
Чтобы сделать стили глобальными, используйте :global(...):
<style>
:global(body) {
margin: 0;
}
</style>
4. Реактивность
4.1. Простая реактивность
Когда вы изменяете переменную, Svelte автоматически обновляет DOM.
<script>
let count = 0;
function increment() {
count += 1;
}
</script>
<button on:click={increment}>
Нажали {count} {count === 1 ? 'раз' : 'раз'}
</button>
4.2. Реактивные выражения
С помощью $: вы можете создать зависимость — выражение будет пересчитываться при изменении зависимых переменных.
<script>
let count = 0;
$: doubled = count * 2;
$: console.log(`count стало ${count}`);
</script>
<p>{count} * 2 = {doubled}</p>
Реактивные блоки могут содержать несколько инструкций:
$: {
console.log('обновление');
if (count > 10) alert('Много!');
}
4.3. Обновление массивов и объектов
Svelte срабатывает на присваивание. Для массивов и объектов нужно создавать новую ссылку:
<script>
let items = [1, 2, 3];
function addItem() {
items = [...items, items.length + 1]; // ✅
// items.push(4); // ❌ не обновит DOM
}
</script>
5. Обработка событий
Синтаксис: on:событие={обработчик}.
<script>
let x = 0, y = 0;
function handleMove(event) {
x = event.clientX;
y = event.clientY;
}
</script>
<div on:mousemove={handleMove}>
Координаты: {x}, {y}
</div>
Можно передавать параметры через замыкание или каррирование:
<button on:click={() => alert('Привет!')}>Нажми</button>
Модификаторы событий (добавляются через |):
- once – выполнить один раз
- preventDefault
- stopPropagation
- self – сработает, только если цель события сам элемент
- capture – фаза перехвата
Пример: on:click|once|preventDefault={handler}.
6. Привязка данных (bind)
Двустороннее связывание с помощью bind:property.
6.1. Поля ввода
<script>
let name = '';
</script>
<input bind:value={name}>
<p>Привет, {name}!</p>
6.2. Чекбоксы и радио
<script>
let isAgreed = false;
let selected = 'option1';
</script>
<input type="checkbox" bind:checked={isAgreed}>
<input type="radio" value="option1" bind:group={selected}>
<input type="radio" value="option2" bind:group={selected}>
6.3. Текстовые области, select
<select bind:value={selected}>
<option value="a">A</option>
<option value="b">B</option>
</select>
6.4. bind:this (ссылка на DOM-узел)
<script>
let canvasElement;
onMount(() => {
const ctx = canvasElement.getContext('2d');
// рисование...
});
</script>
<canvas bind:this={canvasElement} width={200} height={200}></canvas>
7. Пропсы (компонент с параметрами)
7.1. Экспорт переменной (export let)
Любая переменная, отмеченная export let, становится пропсом.
Button.svelte
<script>
export let color = 'blue'; // значение по умолчанию
export let label;
</script>
<button style="background: {color}">
{label}
</button>
App.svelte
<script> import Button from './Button.svelte'; </script> <Button label="Кликни" color="red"/> <Button label="Обычный"/>
7.2. Распространение пропсов ($$props, $$restProps)
<script>
export let id;
export let title;
// все остальные пропсы соберутся в $$restProps
</script>
<button {...$$restProps} id={id}>
{title}
</button>
8. Логические блоки
8.1. {#if} ... {:else if} ... {:else}
{#if user.loggedIn}
<p>Добро пожаловать, {user.name}!</p>
{:else if user.guest}
<p>Войдите как гость</p>
{:else}
<button>Войти</button>
{/if}
8.2. {#each} для списков
<script>
let cats = ['Мурка', 'Барсик', 'Снежок'];
</script>
<ul>
{#each cats as cat, index}
<li>{index + 1}: {cat}</li>
{/each}
</ul>
Ключи для эффективного обновления:
{#each items as item (item.id)}
<div>{item.name}</div>
{/each}
8.3. {#await} для промисов
<script>
let promise = fetchData();
</script>
{#await promise}
<p>Загрузка...</p>
{:then data}
<p>Результат: {data}</p>
{:catch error}
<p>Ошибка: {error.message}</p>
{/await}
9. Хранилища (Stores)
Хранилища (stores) — это реактивные объекты, которые могут использоваться в любых компонентах.
9.1. Создание хранилища
// stores.js
import { writable } from 'svelte/store';
export const count = writable(0); // начальное значение 0
export const user = writable({ name: 'Анна' });
9.2. Чтение и изменение в компонентах
<script>
import { count } from './stores.js';
import { onDestroy } from 'svelte';
// подписка на store (автоматическая)
let value;
const unsubscribe = count.subscribe(v => value = v);
onDestroy(unsubscribe);
function increment() {
count.update(n => n + 1);
// или count.set(5);
}
</script>
<button on:click={increment}>{value}</button>
9.3. Автоматическая подписка через $
Самое удобное: используйте префикс $ перед именем хранилища — Svelte сам подпишется и отпишется.
<script>
import { count } from './stores.js';
function increment() {
$count += 1;
}
</script>
<button on:click={increment}> {$count} </button>
$count можно не только читать, но и присваивать — это вызовет set().
9.4. Другие типы хранилищ: readable, derived
- readable — только для чтения.
- derived — создаёт зависимое хранилище (как вычисляемые значения).
import { derived } from 'svelte/store';
export const double = derived(count, $count => $count * 2);
10. Жизненный цикл компонента
Svelte предоставляет несколько функций, которые нужно импортировать из 'svelte'.
- onMount – вызывается после того, как компонент впервые отрендерен. Подходит для AJAX-запросов, работы с DOM.
- beforeUpdate / afterUpdate – перед / после обновления DOM.
- onDestroy – при уничтожении компонента (очистка таймеров, отписки).
<script>
import { onMount, onDestroy } from 'svelte';
let interval;
let seconds = 0;
onMount(() => {
interval = setInterval(() => seconds++, 1000);
});
onDestroy(() => {
clearInterval(interval);
});
</script>
<p>Прошло {seconds} секунд</p>
11. Переходы и анимации
11.1. Директивы переходов
Импортируйте встроенные переходы: fade, fly, slide, scale, blur и другие.
<script>
import { fade } from 'svelte/transition';
let visible = true;
</script>
<button on:click={() => visible = !visible}>
Переключить
</button>
{#if visible}
<div transition:fade={{ duration: 500 }}>
Появляюсь с затуханием
</div>
{/if}
Можно использовать in: и out: для разных переходов при появлении и скрытии.
11.2. Анимация движений (animate)
Используется в {#each} для анимации перестановки элементов:
<script>
import { flip } from 'svelte/animate';
let items = [1,2,3];
function shuffle() {
items = items.sort(() => Math.random() - 0.5);
}
</script>
<button on:click={shuffle}>Перемешать</button>
{#each items as item (item)}
<div animate:flip>
{item}
</div>
{/each}
12. Слоты (Slots)
Используются для композиции компонентов (как children в React).
12.1. Обычный слот
Card.svelte
<div class="card"> <slot></slot> </div>
App.svelte
<Card> <p>Любое содержимое</p> </Card>
12.2. Именованные слоты
<!-- Layout.svelte --> <header> <slot name="header"></slot> </header> <main> <slot></slot> </main> <footer> <slot name="footer"></slot> </footer>
Использование:
<Layout> <h1 slot="header">Заголовок</h1> <p>Основной контент</p> <p slot="footer">Подвал</p> </Layout>
12.3. Пропсы слотов (slot props)
Позволяют дочернему компоненту передавать данные обратно в слот.
List.svelte
<script>
export let items = [];
</script>
<ul>
{#each items as item}
<li>
<slot {item}></slot>
</li>
{/each}
</ul>
App.svelte
<List items={['яблоко', 'банан']} let:item>
{item.toUpperCase()}
</List>
13. Работа с классами и стилями
13.1. Директива class:
<script>
let active = false;
</script>
<div class:active={active} class:highlight={true}>
Текст
</div>
Для краткости можно писать class:active — это равносильно class:active={active}.
13.2. Динамические inline-стили
<div style="color: {color}; background: {bg};">
Контент
</div>
Безопасно использовать объекты: style={{ color, backgroundColor: 'red' }}.
14. Модули и контекст
14.1. setContext / getContext
Позволяют передавать данные глубоко в дерево без «prop drilling».
<!-- Parent.svelte -->
<script>
import { setContext } from 'svelte';
setContext('theme', 'dark');
</script>
<!-- Child.svelte -->
<script>
import { getContext } from 'svelte';
const theme = getContext('theme');
</script>
15. SvelteKit (фреймворк для полноценных приложений)
SvelteKit — официальный фреймворк, построенный на Svelte, с маршрутизацией, SSR, статической генерацией и т.д.
Основные особенности:
- Файловая маршрутизация (на основе +page.svelte)
- Серверные загрузчики данных (+page.server.js)
- Режимы: SPA, SSR, SSG
Создание проекта SvelteKit:
npm create svelte@latest my-app cd my-app npm install npm run dev
16. Советы и лучшие практики
- Не бойтесь реактивных выражений $: – они делают код чистым и декларативным.
- Используйте bind:value для форм – это сильно упрощает двустороннюю привязку.
- Хранилища через $ – удобно, но не злоупотребляйте глобальным состоянием.
- Компоненты должны быть небольшими, логика и стили рядом.
- Для условного рендеринга используйте {#if}, а не тернарные операторы в разметке.
- Для списков всегда задавайте уникальные ключи во {#each}.
- SvelteKit рекомендуется для проектов сложнее лендинга.
17. Пример: небольшое приложение «Счётчик задач»
<script>
let tasks = [
{ id: 1, text: 'Изучить Svelte', done: false },
{ id: 2, text: 'Написать пособие', done: false }
];
let newTask = '';
function addTask() {
if (!newTask.trim()) return;
tasks = [...tasks, { id: Date.now(), text: newTask, done: false }];
newTask = '';
}
function toggleDone(id) {
tasks = tasks.map(t => t.id === id ? { ...t, done: !t.done } : t);
}
</script>
<h1>Задачи</h1>
<input bind:value={newTask} on:keypress={e => e.key === 'Enter' && addTask()}>
<button on:click={addTask}>Добавить</button>
<ul>
{#each tasks as task (task.id)}
<li>
<label>
<input type="checkbox" bind:checked={task.done} on:change={() => toggleDone(task.id)}>
<span class:done={task.done}>{task.text}</span>
</label>
</li>
{/each}
</ul>
<style>
.done {
text-decoration: line-through;
opacity: 0.6;
}
</style>
Поздравляю! Вы изучили основы Svelte. Это мощный и элегантный инструмент, который особенно хорош для небольших и средних проектов. Дальше — практика и знакомство с SvelteKit для создания полноценных веб-приложений. Удачи!