Skip to content

Сторы

Стор хранит значение модели. Если приложению нужно что-то помнить между событиями, это почти всегда стор: текст поля, выбранный id, список сообщений, статус формы, кешированные данные.

ts
const query = store("");
const profile = store({ name: "Ada", age: 36 });

Стор не является глобальной переменной. Он описывает, какое значение существует в модели, а конкретное значение лежит в scope. Поэтому один и тот же стор может иметь разные значения в разных scopes.

ts
const first = scope();
const second = scope();

scoped(first, () => {
  query.value = "docs";
});

scoped(second, () => {
  query.value = "api";
});

В первом scope у query будет "docs", во втором — "api". Код модели при этом один и тот же.

Чтение и запись

Сторы с примитивными значениями читаются через .value.

ts
count.value += 1;

Стор, хранящий объект, всё равно читается через .value. Чтобы обновить его, присвойте новое значение:

ts
profile.value = { ...profile.value, age: profile.value.age + 1 };

Для объектного состояния, которое нужно читать и писать по полям, используйте reactive (см. раздел «Реактивные сторы» ниже).

Глубоко вложенное состояние

Для состояния, которое вы правите на месте, вглубь — документы, модели редакторов, большие таблицы с правкой по ячейкам — см. @virentia/mutable. Его .value — глубоко мутабельный draft (doc.value.blocks[3].items[7].text = "…") с той же реактивностью по keypath: производное значение перезапустится только для прочитанных им частей.

Прямое чтение или запись требуют scope в текущем контексте выполнения. Если код не находится внутри реакции, effect handler или другой ветки, где scope уже известен, откройте его через scoped(scope, fn).

Записи в сторы транзакционные. Несколько записей в одном синхронном дереве вызовов юнитов батчатся в один commit, а реакции видят финальное закоммиченное значение.

ts
reaction({
  on: incremented,
  run() {
    count.value++;
    count.value++;
  },
});

Внутри транзакции следующий код читает текущее draft-значение. Снаружи транзакции подписчики и derived-сторы уведомляются после commit.

Полная модель исполнения описана в разделе Транзакции.

Реактивные сторы

reactive — это стор, поля объекта которого читаются и пишутся напрямую на самом юните, без обёртки .value. Используйте его, когда состояние — это объект, и обновления по полям читаются естественнее, чем замена значения целиком.

ts
const form = reactive({ name: "Ada", age: 36 });

form.age += 1;
form.name = "Grace";

reactive подчиняется тем же правилам, что и store: значение живёт в scope, прямые чтение и запись требуют активного scope, а записи транзакционные.

Используйте readonlyReactive для объекта, который модель показывает наружу, но меняет только через свои реакции или эффекты. Потребители могут читать поля, но не могут им присваивать.

ts
const profile = readonlyReactive({ name: "Ada", age: 36 });

Оба принимают необязательный skip-токен и devtools-опции, как store.

Derived-сторы

Derived-стор нужен, когда значение полностью следует из другого значения. Например, label поиска можно вычислить из query, а не обновлять вручную в каждой реакции.

ts
const queryLabel = query.map((text) => (text ? `Searching: ${text}` : "Search"));

Derived-сторы ленивые. Если на такой стор не подписана реакция или UI, изменение source-стора только помечает его кеш грязным. Пересчет произойдет позже, когда значение явно прочитают. Если derived-стор активен, например на него подписана реакция, изменение зависимостей сразу пересчитает значение и запустит подписчиков только при реальном изменении результата.

Если значение зависит от события во времени, это уже не derived-стор. Тогда лучше хранить обычный стор и менять его через реакцию.

Ленивые вычисления

computed нужен для значений, которые выводятся из состояния, но не должны пересчитываться до чтения. Это полезно для тяжелой фильтрации, сортировки, сборки view-model или правил, где зависимости удобнее определить по фактическим чтениям.

ts
const query = store("");
const users = store({ items: [] as User[] });

const visibleUsers = computed(() => {
  const text = query.value.toLowerCase();

  return users.value.items.filter((user) => user.name.toLowerCase().includes(text));
});

visibleUsers ведет себя как read-only стор. Первый read в scope выполнит функцию и запомнит результат. Следующие чтения вернут кеш. Когда query или users изменятся в этом же scope, кеш станет грязным, но значение не пересчитается, пока его снова не прочитают или пока его не наблюдает реакция, подписка или UI.

Обычный store() считается активным всегда: запись должна сохранить новое значение. Derived-сторы активируются только наблюдением. Используйте map, когда нужен простой derived-стор от одного источника. Используйте computed, когда вычисление дорогое, зависит от нескольких сторов или зависит от веток внутри самой функции.