Skip to content

Зависимости

Зависимость — это per-scope инъекция: API-клиент, часы, логгер, читалка фича-флагов. Это то, что нужно модели для работы, но это не состояние. Каждый scope предоставляет свой экземпляр: настоящий клиент в проде, мок в тесте, заглушку в предпросмотре.

В отличие от стора, зависимость никогда не сериализуется и не гидрируется. Значения сторов — это состояние, которое вы отправляете с сервера на клиент и восстанавливаете; зависимости — это обвязка, поэтому они живут отдельно и не попадают в этот снапшот.

Определение зависимости

dependency<T>(name?) объявляет её. Как сторы и события, это стабильное определение — конкретный экземпляр живёт в scope.

ts
import { dependency } from "@virentia/core";

interface ApiClient {
  get(id: string): Promise<Item>;
}

const api = dependency<ApiClient>("api");

Необязательное имя используется только для читаемости ошибок и диагностики.

Предоставление зависимости

Предоставьте экземпляр при создании scope или императивно на существующем scope через provideDependency.

ts
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.

ts
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-клиент или логгер нельзя сериализовать, их нужно заново предоставить с каждой стороны границы.

ts
// Сервер: предоставляем серверный клиент, гидрируем состояние как обычно.
const serverScope = scope({
  values: [[user, loadedUser]],
  deps: [[api, serverApiClient]],
});

// Клиент: та же модель, свой экземпляр клиента, гидрированное состояние.
const clientScope = scope({
  values: hydratedValues,
  deps: [[api, browserApiClient]],
});

Используйте зависимость для всего, чем scope должен владеть, но что нельзя персистить: HTTP-клиенты, сокеты, таймеры/часы, источники случайности, логгеры, фича-флаги. Используйте store для состояния, которое scope должен помнить и уметь сериализовать.