Общее состояние через .svelte.js-модули

Чтобы делить состояние между компонентами, вынесите руны в .svelte.js-модуль — универсальная реактивность работает и вне компонентов.

«Состояние не должно быть заперто в компоненте.» Руны в отдельном модуле дают общее реактивное состояние без церемоний.

Локальное состояние внутри компонента — это хорошо, но приложению нужно общее состояние: текущий пользователь, корзина, тема оформления. В Svelte 5 это решается изящно: руны работают не только в компонентах, но и в обычных модулях с расширением .svelte.js (или .svelte.ts). Вы создаёте состояние через $state в таком файле, экспортируете и импортируете где угодно — реактивность сохраняется.

Это и есть «универсальная реактивность», главное преимущество рун над старыми сторами. Раньше для общего состояния обязательно нужен был стор. Теперь во многих случаях достаточно вынести руны в файл. Чтобы избежать ловушек реактивности при импорте, общее состояние удобно оборачивать в объект и экспортировать функции-аксессоры или сам объект.

// counter.svelte.js — общее реактивное состояние
export const cart = $state({ items: [], total: 0 });

export function addItem(item) {
  cart.items.push(item);
  cart.total += item.price;
}

Теперь любой компонент импортирует это состояние и видит его реактивно:

<!-- любой компонент -->
<script>
  import { cart, addItem } from './counter.svelte.js';
</script>

<p>Товаров: {cart.items.length}, сумма: {cart.total}</p>
<button onclick={() => addItem({ name: 'Чай', price: 50 })}>Добавить</button>

Важная тонкость: не экспортируйте напрямую примитивное $state-значение (например, число), потому что при импорте вы получите снимок, потерявший реактивность. Оборачивайте состояние в объект (как cart выше) либо экспортируйте геттеры — тогда связь сохраняется.

Как это работает под капотом

Общее состояние — это один объект-источник, на который ссылаются разные компоненты. Все они читают и пишут один и тот же прокси, поэтому изменения видны всем. Смоделируем модуль-синглтон.

// Общий модуль-синглтон: все импортёры делят один объект
const cart = { items: [], total: 0, _subs: new Set() };
function notify() { cart._subs.forEach(f => f()); }
function addItem(item) { cart.items.push(item); cart.total += item.price; notify(); }
function subscribe(fn) { cart._subs.add(fn); }

// 'Компонент A' и 'Компонент B' подписаны на один и тот же cart
subscribe(() => console.log('A видит сумму:', cart.total));
subscribe(() => console.log('B видит товаров:', cart.items.length));

addItem({ name: 'Чай', price: 50 });   // оба компонента обновились
addItem({ name: 'Кофе', price: 80 });  // снова оба

Попробуй сам ▶ — вставь код в консоль браузера (F12 → Console) и нажми Enter, чтобы увидеть вывод.

           counter.svelte.js
            ($state cart)
          /       |       \
  Компонент A  Компонент B  Компонент C
  (все ссылаются на один реактивный объект)

Частые ошибки

  • Класть руны в обычный .js-файл. Нужен .svelte.js / .svelte.ts.
  • Экспортировать примитивное $state напрямую. При импорте теряется реактивность — оборачивайте в объект.
  • Мутировать общее состояние из разных мест бесконтрольно. Лучше менять его через экспортированные функции.

Best practices

  • Инкапсулируйте общее состояние в модуле и предоставляйте функции для его изменения.
  • Оборачивайте состояние в объект, чтобы сохранить реактивность при импорте.
  • Для серверного рендеринга помните: глобальный модуль общий на сервере — не храните в нём данные одного пользователя.

Граница между локальным и общим

Появление универсальной реактивности соблазняет выносить в общие модули как можно больше состояния — но это ловушка. Чем больше глобального состояния, тем труднее рассуждать о приложении: любое значение может измениться откуда угодно, и отследить источник бага становится сложно. Здравый ориентир — держать состояние настолько локальным, насколько возможно, и поднимать в общий модуль только то, что действительно нужно нескольким несвязанным частям приложения: текущий пользователь, корзина, тема оформления, уведомления. Если двум соседним компонентам нужно общее значение, часто достаточно поднять его в их общего родителя и передать пропсами, а не делать глобальным. Отдельно держите в голове серверный рендеринг: глобальный модуль на сервере общий для всех запросов, поэтому в него нельзя класть данные конкретного пользователя — для этого служит контекст. Грамотное разделение на локальное, поднятое и общее состояние — один из главных навыков, отличающих поддерживаемое приложение от запутанного.

Итог: вынос рун в .svelte.js-модуль даёт общее реактивное состояние между компонентами. Оборачивайте состояние в объект и меняйте через функции — это безопасно и переиспользуемо.

Проверьте себя
1. Как сделать реактивное состояние общим для нескольких компонентов в Svelte 5?
AТолько через классические сторы — другого пути нет
BВынести руны в .svelte.js-модуль и импортировать состояние
CЗаписать его в localStorage
DПередать пропсами через все уровни
2. Почему примитивное $state-значение нельзя экспортировать напрямую для общего использования?
AЭто синтаксическая ошибка
BПри импорте получается снимок, теряющий реактивность — нужно оборачивать в объект
CПримитивы нельзя экспортировать в JS
DЭто замедляет сборку