@vr1/stateful
v1.0.1
Published
Библиотека для хранения и чистого(pure), иммутабельного изменения состояния фронтенда.
Downloads
195
Readme
@vr1/stateful
Библиотека для управления состоянием фронтенда с чистыми (pure) и иммутабельными обновлениями. Предоставляет простой и надёжный способ хранить состояние приложения, подписываться на его изменения и обновлять его без побочных эффектов.
Описание
@vr1/stateful — это минималистичная библиотека для управления состоянием в фронтенд-приложениях. Она обеспечивает:
- Чистые обновления — состояние изменяется через
положи()с новыми значениями, без мутаций исходного объекта. - Иммутабельность — каждое обновление создаёт новый объект состояния.
- Реактивность — подписка на изменения состояния с автоматическими уведомлениями.
- Транзакции — группировка нескольких изменений в одну атомарную операцию.
- Поддержка вложенных хранилищ — возможность создавать иерархические структуры состояния.
Установка
npm install @vr1/statefulОсновные концепции
- Хранилище — объект, который содержит состояние и методы для работы с ним.
- Иммутабельность — все обновления создают новые объекты, не изменяя исходные.
- Подписка — механизм уведомления об изменениях состояния.
- Транзакции — группировка изменений для оптимизации производительности.
- Путь — массив строк, показывающий положение дочернего хранилища в иерархии (например,
["поле1", "поле3"]).
Примеры использования
Базовое использование
import { создай_хранилище } from "@vr1/stateful";
// Создание хранилища с начальным состоянием
const счётчик = создай_хранилище({ количество: 0 });
// Подписка на изменения
счётчик.подпишись((новое_состояние) => {
console.log("Новое количество:", новое_состояние.количество);
});
// Обновление состояния
счётчик.положи({ количество: 1 }); // Выведет: Новое количество: 1
счётчик.положи({ количество: 2 }); // Выведет: Новое количество: 2Работа с объектами состояния
import { создай_хранилище } from "@vr1/stateful";
interface ПрофильПользователя {
имя: string;
email: string;
возраст: number;
}
const профиль = создай_хранилище<ПрофильПользователя>({
имя: "Иван",
email: "[email protected]",
возраст: 25,
});
// Доступ к текущему состоянию
console.log(профиль.имя); // "Иван"
console.log(профиль.email); // "[email protected]"
// Обновление отдельных полей
профиль.положи({ возраст: 26 });
// Обновление нескольких полей одновременно
профиль.положи({
имя: "Иван Петров",
возраст: 27,
});Получение текущего состояния через метод текущее()
Метод текущее() используется для получения полного объекта состояния в момент вызова. Это необходимо когда нужно:
- Читать актуальное состояние внутри асинхронных операций — например, получить текущее значение перед обновлением
- Вычислять новые значения на основе старого состояния — использовать текущие данные для расчёта
- Избежать замыкания на переменные — всегда получить свежее состояние в callback'ах
import { создай_хранилище } from "@vr1/stateful";
const счётчик = создай_хранилище({ x: 0 });
счётчик.подпишись((новое) => {
console.log("Значение x:", новое.x);
});
// Асинхронная функция, которая получает текущее состояние и обновляет его
const увеличить_асинхронно = async (задержка_мс: number) => {
await new Promise(resolve => setTimeout(resolve, задержка_мс));
// Используем текущее() для получения актуального значения
const текущее_значение = счётчик.текущее().x;
счётчик.положи({ x: текущее_значение + 1 });
};
// Вызываем несколько асинхронных операций параллельно
await Promise.all([
увеличить_асинхронно(30),
увеличить_асинхронно(10),
увеличить_асинхронно(50),
]);
// Подписка сработает три раза: x = 1, 2, 3Почему это важно:
- Если просто написать
счётчик.положи({ x: счётчик.x + 1 }), есть риск использовать старое значение из-за замыкания счётчик.текущее()всегда возвращает актуальное состояние на момент вызова- Особенно полезно в асинхронных операциях и параллельных вызовах
Синхронные транзакции
Группировка нескольких изменений в одну операцию. Подписка срабатывает только один раз после завершения всей транзакции.
import { создай_хранилище } from "@vr1/stateful";
const приложение = создай_хранилище({
загружается: false,
ошибка: null as string | null,
данные: null as object | null,
});
приложение.подпишись((новоеХранилище) => {
console.log("Состояние изменилось");
});
// Все три изменения произойдут в одной транзакции
приложение.транзакция(() => {
приложение.положи({ загружается: true });
приложение.положи({ ошибка: null });
приложение.положи({ данные: { id: 1 } });
});
// Подписка сработает только один раз, несмотря на три положи()Асинхронные транзакции
Транзакции поддерживают асинхронные операции. Подписка срабатывает только после завершения асинхронного действия.
import { создай_хранилище } from "@vr1/stateful";
const магазин = создай_хранилище({
загружается: false,
товары: [] as { id: number; название: string }[],
ошибка: null as string | null,
});
магазин.подпишись((новоеХранилище) => {
console.log("Товары обновлены");
});
// Асинхронная загрузка товаров
async function загрузить_товары() {
await магазин.транзакция(async () => {
магазин.положи({ загружается: true });
try {
const ответ = await fetch("/api/товары");
const товары = await ответ.json();
магазин.положи({ товары, ошибка: null });
} catch (ошибка) {
магазин.положи({
ошибка: (ошибка as Error).message || "Ошибка загрузки",
});
} finally {
магазин.положи({ загружается: false });
}
});
}
await загрузить_товары();
// Подписка сработает один раз после завершения всего процессаВложенные хранилища
Хранилища могут содержать другие хранилища, создавая иерархическую структуру состояния.
import { создай_хранилище } from "@vr1/stateful";
// Создание вложенного хранилища для параметров пользователя
const параметры_пользователя = создай_хранилище({
тема: "светлая" as "светлая" | "тёмная",
язык: "русский" as "русский" | "английский",
уведомления: true,
});
// Создание главного хранилища
const приложение = создай_хранилище({
пользователь: null as { id: number; имя: string } | null,
параметры: параметры_пользователя,
});
приложение.подпишись((новоеХранилище) => {
console.log("Состояние приложения обновлено");
});
// Изменения вложенного хранилища автоматически пропагируются вверх
приложение.параметры.положи({ тема: "тёмная" });
// Подписка на приложение сработает с новым значением параметровВажно: Автоматическое копирование вложенных хранилищ
При создании хранилища с вложенными хранилищами автоматически создаются независимые копии каждого вложенного хранилища. Это значит, что если использовать одно и то же хранилище в нескольких полях, каждое поле получит свою отдельную копию, полностью независимую от оригинала и других копий:
import { создай_хранилище, Хранилище } from "@vr1/stateful";
// Создание одного хранилища для позиции
const позиция = создай_хранилище({ x: 0, y: 0 });
interface Состояние {
левая_позиция: Хранилище<{ x: number; y: number }>;
правая_позиция: Хранилище<{ x: number; y: number }>;
}
// Использование одного хранилища позиции в двух местах
const окна = создай_хранилище<Состояние>({
левая_позиция: позиция,
правая_позиция: позиция,
});
окна.подпишись((новоеСостояние) => {
console.log("Окна обновлены");
console.log("Левое окно:", новоеСостояние.левая_позиция);
console.log("Правое окно:", новоеСостояние.правая_позиция);
});
// Изменение левого окна НЕ влияет на правое
окна.левая_позиция.положи({ x: 100 });
// Левое окно: { x: 100, y: 0 }
// Правое окно: { x: 0, y: 0 }
// Изменение правого окна НЕ влияет на левое
окна.правая_позиция.положи({ y: 50 });
// Левое окно: { x: 100, y: 0 }
// Правое окно: { x: 0, y: 50 }Как это работает:
- Каждое поле хранилища получает свою собственную копию вложенного хранилища
- Изменения в копии не влияют на другие копии этого же хранилища
- Изменения в оригинальном хранилище не влияют на копии (они полностью независимы)
- Это обеспечивает полную изоляцию состояния в разных частях приложения
Определение пути в иерархии
Каждое хранилище содержит свойство путь — массив строк, который показывает его положение в иерархии состояния. Корневое хранилище имеет пустой путь [], а каждое вложенное хранилище знает, через какие ключи до него добраться от корня.
import { создай_хранилище, Хранилище } from "@vr1/stateful";
// Создание иерархии хранилищ
const настройки = создай_хранилище({ язык: "русский", тема: "светлая" });
const профиль = создай_хранилище({
имя: "Иван",
настройки: настройки,
});
const приложение = создай_хранилище({
профиль: профиль,
});
// Проверка путей
console.log(приложение.путь); // []
console.log(приложение.профиль.путь); // ["профиль"]
console.log(приложение.профиль.настройки.путь); // ["профиль", "настройки"]
// Пути помогают отследить, откуда пришло изменение
приложение.подпишись((новое) => {
console.log("Профиль обновлён:", новое.профиль.путь); // ["профиль"]
console.log("Настройки обновлены:", новое.профиль.настройки.путь); // ["профиль", "настройки"]
});
приложение.профиль.настройки.положи({ язык: "английский" });Зачем нужен путь:
- Отладка — быстро определить, где в дереве состояния произошло изменение
- Логирование — записать полный путь до измененного значения
Правила использования транзакций
Транзакции могут быть вложенными, подписка срабатывает только когда завершилась самая верхняя транзакция:
const состояние = создай_хранилище({ a: 1, b: 2, c: 3 });
состояние.подпишись(() => {
console.log("Обновление");
});
состояние.транзакция(() => {
состояние.положи({ a: 10 });
состояние.транзакция(() => {
состояние.положи({ b: 20 });
// Подписка НЕ сработает здесь
});
состояние.положи({ c: 30 });
// Подписка НЕ сработает и здесь
});
// Подписка сработает один раз после выхода из самой внешней транзакцииЛицензия
MIT
