← Назад к курсу

Пособие по 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. Советы и лучшие практики

  1. Не бойтесь реактивных выражений $: – они делают код чистым и декларативным.
  2. Используйте bind:value для форм – это сильно упрощает двустороннюю привязку.
  3. Хранилища через $ – удобно, но не злоупотребляйте глобальным состоянием.
  4. Компоненты должны быть небольшими, логика и стили рядом.
  5. Для условного рендеринга используйте {#if}, а не тернарные операторы в разметке.
  6. Для списков всегда задавайте уникальные ключи во {#each}.
  7. 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 для создания полноценных веб-приложений. Удачи!