Зависимости
Зависимость — это per-scope инъекция: API-клиент, часы, логгер, читалка фича-флагов. Это то, что нужно модели для работы, но это не состояние. Каждый scope предоставляет свой экземпляр: настоящий клиент в проде, мок в тесте, заглушку в предпросмотре.
В отличие от стора, зависимость никогда не сериализуется и не гидрируется. Значения сторов — это состояние, которое вы отправляете с сервера на клиент и восстанавливаете; зависимости — это обвязка, поэтому они живут отдельно и не попадают в этот снапшот.
Определение зависимости
dependency<T>(name?) объявляет её. Как сторы и события, это стабильное определение — конкретный экземпляр живёт в scope.
import { dependency } from "@virentia/core";
interface ApiClient {
get(id: string): Promise<Item>;
}
const api = dependency<ApiClient>("api");Необязательное имя используется только для читаемости ошибок и диагностики.
Предоставление зависимости
Предоставьте экземпляр при создании scope или императивно на существующем scope через provideDependency.
import { provideDependency, scope } from "@virentia/core";
// При создании.
const appScope = scope({
deps: [[api, new RealApiClient()]],
});
// Или позже, императивно.
const testScope = scope();
provideDependency(testScope, api, new FakeApiClient());Одна и та же модель работает с настоящим клиентом в проде и с фейковым в тесте, и модель не знает разницы.
Чтение зависимости
Читайте её через .value внутри всего, что выполняется под scope — обработчик эффекта, тело реакции или блок scoped(...). Это обычный случай: эффект, которому нужен клиент, читает его из активного scope.
import { effect } from "@virentia/core";
const loadItemFx = effect(async (id: string) => {
return api.value.get(id);
});Чтение зависимости — не реактивная зависимость: зависимость не меняется за время жизни scope, поэтому реакция, читающая её, не перезапускается из-за этого.
Если активный scope не предоставил зависимость, её чтение бросает понятную ошибку с именем зависимости (и цепочкой юнитов, приведшей к чтению, если это произошло внутри обработчика). Предоставьте её через scope({ deps }) или provideDependency.
Не состояние — значит, не сериализуется
Значения сторов лежат в сериализуемом состоянии scope — SSR-снапшот переносит их на клиент и восстанавливает. Зависимости лежат в отдельной карте deps и намеренно исключены из этого снапшота: API-клиент или логгер нельзя сериализовать, их нужно заново предоставить с каждой стороны границы.
// Сервер: предоставляем серверный клиент, гидрируем состояние как обычно.
const serverScope = scope({
values: [[user, loadedUser]],
deps: [[api, serverApiClient]],
});
// Клиент: та же модель, свой экземпляр клиента, гидрированное состояние.
const clientScope = scope({
values: hydratedValues,
deps: [[api, browserApiClient]],
});Используйте зависимость для всего, чем scope должен владеть, но что нельзя персистить: HTTP-клиенты, сокеты, таймеры/часы, источники случайности, логгеры, фича-флаги. Используйте store для состояния, которое scope должен помнить и уметь сериализовать.