Svelte
Stores: интероп с рунами и когда они ещё уместны
Проект на Svelte 4 переезжает на Svelte 5. В нём сотни writable-сторов: текущий пользователь, корзина, поток WebSocket-сообщений. Переписать всё на руны разом нереально, а новый код хочется писать на рунах. Хорошая новость: сторы и руны сосуществуют. Старый стор можно прочитать в новом руническом компоненте через мост, а рунное состояние превратить в стор для старого кода. Миграция идёт постепенно, файл за файлом, без остановки разработки.
- Миграция кодовых баз с Svelte 4 на 5: новые компоненты на рунах читают старые сторы через fromStore
- Библиотеки: пакеты, опубликованные с store-based API, потребляются как руническими, так и легаси-компонентами
- Потоки событий: стор поверх WebSocket или EventSource удобно раздаёт значения во времени многим подписчикам
- Глобальные синглтоны вне дерева компонентов: стор работает там, где руны компонента недоступны
Предварительные знания
- Понимание реактивного состояния через руну `$state`
- Базовое знание паттерна подписки: подписался, получаешь обновления, отписался
- Представление о том, что такое модуль и как из него экспортируют значения
writable, readable, derived и авто-подписка
Стор в Svelte это объект, реализующий контракт подписки: у него есть метод subscribe(callback), который вызывает callback при каждом изменении значения и возвращает функцию отписки. Стандартная библиотека даёт три фабрики. writable(initial) создаёт стор, который можно менять через set и update. readable(initial, start) создаёт стор только для чтения, источник которого задаётся извне. derived(stores, fn) вычисляет значение из одного или нескольких других сторов.
В компоненте префикс доллара перед именем стора это синтаксис авто-подписки. Запись `$count` в разметке или скрипте читает текущее значение стора и подписывает компонент на изменения, а при размонтировании Svelte отписывается сам. Без доллара count это сам объект-стор, а `$count` это его значение. Этот синтаксис избавляет от ручного вызова subscribe и unsubscribe.
Авто-подписка через доллар работает только внутри компонента .svelte. В обычном .js или .ts-модуле префикс доллара недоступен, и стор нужно читать через get(store) или подписку вручную с обязательной отпиской. Это одно из ограничений, из-за которых руны в .svelte.ts часто удобнее.
Что именно делает запись `$count` в компоненте при использовании writable-стора count?
Мосты fromStore и toStore
Svelte 5 предоставляет две функции для интеропа между сторами и рунами. fromStore(store) принимает стор и возвращает рунный объект с реактивным полем current, которое отражает значение стора. toStore(getter, setter) делает обратное: оборачивает рунное состояние в объект-стор, который старый код может читать через subscribe или через доллар. Вместе они позволяют двум мирам сосуществовать в одной кодовой базе.
fromStore удобен, когда новый рунический компонент должен потреблять стор из существующей библиотеки или старого модуля: поле current ведёт себя как обычное реактивное значение. toStore полезен в обратную сторону: новый модуль хранит состояние в руне, но экспортирует его как стор, чтобы легаси-компоненты, ожидающие store-контракт, продолжали работать без переписывания.
| Функция | Вход | Выход | Когда применять |
|---|---|---|---|
| fromStore | Стор | Рунный объект с .current | Читать легаси-стор в новом коде на рунах |
| toStore | Геттер и сеттер руны | Объект-стор | Дать рунное состояние старому коду как стор |
Интероп нужен не навсегда. Это инструмент постепенной миграции: пока часть кода на сторах, а часть на рунах, мосты держат их вместе. По мере переписывания на руны вызовы fromStore и toStore постепенно убираются.
Новый компонент на рунах должен прочитать значение из writable-стора, экспортированного старой библиотекой. Какой инструмент подходит и что он даст?
Когда стор всё ещё уместнее руны
Руны теперь предпочтительный способ работы с состоянием, но сторы не устарели и в ряде случаев удобнее. Контракт стора это поток значений во времени с явной логикой запуска и остановки. Это хорошо ложится на источники, которые надо подключить при первой подписке и отключить при последней: WebSocket, EventSource, таймер, геолокация. readable со start-функцией инкапсулирует такой жизненный цикл аккуратнее, чем руны.
Функция start здесь вызывается, когда появляется первый подписчик, а возвращённая из неё функция останавливает таймер, когда подписчиков не осталось. Такой автоматический жизненный цикл потока встроен в контракт стора. Второе преимущество стора это работа вне дерева компонентов: стор это просто объект, его можно читать и в обычном .js-модуле через get или subscribe, тогда как руны компонента к дереву привязаны.
| Задача | Лучше выбрать | Почему |
|---|---|---|
| Локальное состояние компонента | Руны | `$state` точечнее и проще, без церемонии подписки |
| Поток событий с запуском и остановкой | Стор | start/stop в readable инкапсулирует жизненный цикл источника |
| Производное значение в компоненте | Руны | `$derived` встроен в реактивность компонента |
| Сквозной поток для многих подписчиков | Стор | Контракт subscribe работает и вне дерева компонентов |
Практический ориентир: для нового состояния по умолчанию берут руны. Сторы достают, когда задача это именно поток значений во времени с управляемым жизненным циклом источника, либо когда реактивность нужна там, где рун компонента нет.
Нужен реактивный источник, который подключается к WebSocket при появлении первого подписчика и закрывает соединение, когда подписчиков не осталось. Что выбрать и почему?
Связь с другими темами
Сторы это предшественник рун для реактивности. Они соседствуют с сигнальной моделью и потоками данных:
- Реактивное состояние — Руны это современная замена сторов для большинства задач состояния
- Тонкая реактивность — Сигнальная модель объясняет, чем подписка стора отличается от точечной реактивности рун
- Асинхронные данные — Сторы исторически сильны в потоках значений во времени, типичных для асинхронных источников
Итог
- Стор это объект с методом subscribe: writable можно менять, readable только читать, derived вычисляется из других сторов
- Префикс доллара перед именем стора в компоненте даёт авто-подписку и авто-отписку: `$count` читает значение и обновляется
- fromStore оборачивает стор в рунный объект с полем current, а toStore превращает рунное состояние обратно в стор
- Руны теперь предпочтительный способ состояния, но сторы остаются рабочими и удобны для сквозных потоков значений
- Стор всё ещё выигрывает там, где нужен поток событий во времени или реактивность вне дерева компонентов
Связанные уроки
- sv-06-state — Сторы и руны решают одну задачу реактивного состояния, поэтому базовое понимание `$state` необходимо для сравнения
- sv-30-fine-grained-reactivity — И сторы, и руны опираются на идею подписки на изменения, и сигнальная модель помогает понять различия
- sv-34-async-data — Сторы исторически удобны для потоков значений во времени, что прямо пересекается с асинхронными данными