Лучшие практики работы со Svelte
Если вы уже освоили основы Svelte и начали создавать реальные приложения, вы наверняка заметили, что официальная документация рассказывает, что делают те или иные инструменты, но не всегда — когда и почему их стоит использовать. Эта статья посвящена лучшим практикам Svelte 5, которые повышают поддерживаемость, производительность и ясность production-кода, и предполагает, что вы уже понимаете, как работают компоненты и реактивность.
Ключевые выводы
- Используйте
$stateтолько тогда, когда значение управляет обновлениями UI, а$state.raw— когда вы заменяете значения целиком, а не мутируете их. - Предпочитайте
$derivedвместо$effectдля вычисляемых значений; оставляйте$effectдля синхронизации с внешними системами. - Избегайте состояния на уровне модулей в SSR-окружениях. Используйте context API Svelte вместе с классами на основе
$stateдля типобезопасного, привязанного к запросу разделяемого состояния. - В SvelteKit используйте
+page.server.jsдля серверных данных страниц и+server.jsдля отдельных API-эндпоинтов. - Применяйте современный синтаксис Svelte 5 (
onclick,{#snippet},$props()) вместо устаревших паттернов в новом коде.
Runes в Svelte 5: используйте их осознанно
Runes в Svelte 5 — это основная модель реактивности, и правильное их применение важнее, чем их повсеместное использование.
Прибегайте к $state только тогда, когда переменная действительно должна управлять обновлениями UI. Обычные переменные дешевле и понятнее во всех остальных случаях.
Если ваше состояние — это большой объект или массив, который заменяется, а не мутируется, используйте $state.raw:
// ❌ Излишние накладные расходы прокси для данных API, которые вы только переприсваиваете
let users = $state(await fetchUsers());
// ✅ Без затрат на прокси, когда вы заменяете, а не мутируете
let users = $state.raw(await fetchUsers());
Используйте $state, когда нужно напрямую мутировать вложенные свойства (например, cart.items[0].quantity++). Используйте $state.raw, когда вы заменяете значение целиком.
Предпочитайте $derived вместо $effect для вычисляемых значений
Это одна из самых распространённых ошибок в современной разработке на Svelte:
let num = $state(0);
// ❌ Избегайте — создаёт ненужный побочный эффект
let square = $state(0);
$effect(() => { square = num * num; });
// ✅ Правильно — декларативно и с отслеживанием зависимостей
let square = $derived(num * num);
$effect — это «аварийный люк». Оставляйте его для синхронизации с внешними системами (например, D3) и рассмотрите {@attach} для интеграций на уровне DOM, где это естественно подходит.
Относитесь к props как к динамическим значениям
Значения, производные от props, должны использовать $derived, а не обычное присваивание:
let { type } = $props();
// ✅ Остаётся синхронизированным при изменении `type`
let color = $derived(type === 'danger' ? 'red' : 'green');
Discover how at OpenReplay.com.
Типобезопасный контекст вместо общих модулей
Для состояния, разделяемого между поддеревом компонентов, предпочитайте context API Svelte вместо состояния на уровне модуля. Состояние модуля сохраняется между запросами в SSR-окружениях, что может привести к утечке данных между пользователями.
Современный паттерн использует класс с полями $state:
// lib/theme.svelte.ts
import { getContext, setContext } from 'svelte';
class ThemeContext {
current = $state('light');
toggle() {
this.current = this.current === 'light' ? 'dark' : 'light';
}
}
const KEY = Symbol('theme');
export const setTheme = () => setContext(KEY, new ThemeContext());
export const getTheme = () => getContext<ThemeContext>(KEY);
Этот подход даёт вам типобезопасность, реактивное состояние и корректную изоляцию для SSR в рамках одного паттерна.
Загрузка данных в SvelteKit: выбор правильного паттерна
Частый источник путаницы в SvelteKit — когда использовать +page.server.js, а когда +server.js:
| Сценарий | Используйте |
|---|---|
| Получение данных для страницы с SSR или серверным доступом | +page.server.js с load() |
| Создание API-эндпоинта для внешнего использования | +server.js |
| Данные только для клиента после гидратации | onMount + fetch |
Для данных страницы, требующих серверного доступа, секретов или SSR, +page.server.js обычно является правильным выбором по умолчанию. Он выполняется на сервере, не пропускает секреты на клиент и чисто интегрируется с form actions SvelteKit для прогрессивного улучшения.
Небольшие практические улучшения
Блоки {#each} с ключами предотвращают незаметные баги переиспользования DOM. Всегда задавайте ключ по стабильному уникальному ID, никогда — по индексу.
$inspect.trace недостаточно используется для отладки реактивности. Добавьте его в начало любого $effect или $derived.by, чтобы точно увидеть, какая зависимость вызвала повторный запуск.
Snippets вместо slots для переиспользуемых фрагментов разметки. Snippets лучше композируются и могут передаваться как props, что делает API компонентов чище.
Избегайте устаревшего синтаксиса в новом коде. Заменяйте on:click на onclick, <slot> на {#snippet}, а export let на $props(). Эти паттерны соответствуют современным соглашениям Svelte 5 и текущему поведению компилятора.
Заключение
Svelte 5 поощряет сдержанность. Чем точнее вы ограничиваете реактивность — используя $state только там, где это нужно, $derived вместо $effect, и контекст вместо глобальных модульных переменных, — тем более предсказуемым и производительным становится ваше приложение. Начинайте с самого простого реактивного примитива, который решает задачу, и переходите к более мощным инструментам только тогда, когда более простые действительно не справляются.
Часто задаваемые вопросы
Используйте $state.raw, когда планируете заменять значение целиком, а не мутировать его части — например, при сохранении ответов API или больших массивов, которые переприсваиваются полностью. Это позволяет избежать накладных расходов реактивного прокси, что улучшает производительность для больших наборов данных. Используйте обычный $state, когда нужна детальная реактивность для вложенных мутаций, например при обновлении элемента внутри массива.
$derived является декларативным, автоматически отслеживает свои зависимости и создаёт значение только для чтения, которое остаётся синхронизированным. $effect выполняется императивно как побочный эффект, и его сложнее анализировать, поскольку он может вносить ошибки тайминга и ненужные повторные запуски. Оставляйте $effect для синхронизации с системами вне реактивности Svelte, такими как сторонние библиотеки, canvas API или ручная работа с DOM.
Не в SSR-окружениях, таких как SvelteKit. Состояние на уровне модуля разделяется между всеми запросами, которые обрабатывает сервер, что может привести к утечке данных одного пользователя в сессию другого. Используйте context API Svelte с setContext и getContext, желательно подкреплённый классом с полями $state. Это ограничивает состояние рамками запроса и дерева компонентов, сохраняя реактивность и типобезопасность.
Используйте +page.server.js, когда данные принадлежат конкретной странице и выигрывают от SSR, SEO, серверного доступа или form actions. Используйте +server.js, когда нужен отдельный HTTP-эндпоинт, например JSON API для внешних клиентов, вебхуки или fetch-запросы вне страниц. Если данные отображаются только одной страницей и требуют серверных возможностей, +page.server.js обычно является лучшим выбором по умолчанию.
Gain Debugging Superpowers
Unleash the power of session replay to reproduce bugs, track slowdowns and uncover frustrations in your app. Get complete visibility into your frontend with OpenReplay — the most advanced open-source session replay tool for developers. Check our GitHub repo and join the thousands of developers in our community.