Comments 23
Еще когда только набирал популярность redux я удивлялся ради чего весь этот огород городить, и с тех пор появляются все новые и новые стейт менеджеры... как будто новички их придумывают даже не разобравшись с тем что браузер им дает из коробки.
Открою всем читателям "революционный" нативный стейт менеджер - indexedDB.
- один источник истины для всех вкладок с сайтом
- доступ к хранилищу из web/shared/service worker
- оффлайн режим
- индексация и моментальный поиск
- десятки тысяч записей без тормозов
- язык запросов(а-ля селекторы) почти как у mongo
- реактивность(при определенном подходе)
- вы можете создавать для одного домена много баз(сторов)
- инструмент отладки стора уже встроен в твой браузер(см. Application->Storage->IndexedDB )
- дружит с любыми фреймворками
- иммутабельней сотен троеточий!
Посмотрите сами https://dexie.org/
трямс - и стор готов
const db = new Dexie('MyDatabase');
db.version(1).stores({
friends: '++id, name, age, avatar',
keyval: 'key,value'
});
подписываемся на обновления коллеции
import { useLiveQuery } from "dexie-react-hooks";
import { db } from "./db";
export function FriendList () {
const friends = useLiveQuery(() => db.friends.where("age").between(18, 65).toArray(););
return <>
{friends?.map(friend =><div key={friend.id}>{friend.name}, {friend.age}<
</div>)}
</>;
}
Ну и "экшон" если говорить понятиями редакса. add / put / bulkPut
await db.friends.add({
name: 'Camilla',
age: 25,
avatar: await getBlob('camilla.png')
});
// ну или
DB.friends.bulkPut( await (await fetch('/api/friends/')).json() )
Пример простого key value
const db = new Dexie(config.db.name)
db.version(config.db.version).stores({
keyval : 'key, value',
})
DB.keyval.put({ key:'currentChainId', value: ethApp.chainId })
где-то в воркере обновляем баланс при изменении id текущего пользователя
liveQuery(() => DB.keyval.get('currentAccount')).subscribe({
next: () => {
updateUserBalances()
}
})
Это разные весовые категории. И, собственно, по весу (~100 kB vs ~2 kB), и по смыслу (персистентное хранилище vs состояние вида), и, соответственно, по памяти/процессору. А так да, тоже недавно открыл для себя Dixie, впечатлился и в восторге :).
IndexedDB - не либа и ни сколько не весит. Я привел в пример dexie как удобную обертку для IndexedDB. Есть другие способы работать с ней - https://habr.com/ru/post/569376/ и другие(наверняка более легковесные) обертки - https://github.com/jakearchibald/idb
Я вообще к тому, что все это уже есть давно нативно, еще до редакса было. Я понимаю еще какие-то движения в сторону "лучшей" реактивности, типа rxjs. Но зачем вот это простое хранение в памяти? Ну пишите сразу в localStorage и из него читайте или любой singleton класс можно сделать с геттерами и сеттерами, да хоть в window.DATA создайте объект и все туда пишите и тоже не нужно оборачивать ничего в провайдер контекста )))
Ну можно сразу в удалённую базу данных писать состояние какого-нибудь слайдера из GUI, зачем останавливаться на полумерах :). Вот ваш вариант с синглтоном - вы не можете помыслить его без IndexedDB, что ли? Вы намешали кучу проблем и кучу методов для их решения, перемешали все архитектурные слои :). Вы правильно вспомнили rxJS - это именно тот архитектурный слой, который вы, кажется, не можете рассмотреть, когда он вырождается до простого сеттера-геттера, и хотите к нему прикрутить в нагрузку "хоть что-то полезное" :). Не надо.
Любой localStorage, а IndexedDB тоже он, плох тем, что 1) сильно медленный, 2) ограниченный 5 МБ по умолчанию настроек клиента.
Хах, классика, идет сравнение с убогими "соперниками" в виде Redux и Recoil, но ни слова про MobX, который на 100 голов выше всей этой шушеры, разумеется включая Zustand. Ибо стейт менеджер не основанный на Getters/Setters (появились в JS в 2010 году) это просто нелепо и смешно.
Идея понятная и, в принципе, на том же Redux можно получить схожий синтаксис. Но как быть с масштабированием?
В реальном проекте в сторе будет лежать куча малосвязанных друг с другом данных и куча функций для их изменения. Чтобы не было нечитаемого и неподдерживаемого спагетти, надо как минимум разнести по разным файлам, а желательно еще и как-то запретить изменения "чужой" части стейта.
Как предлагается решать данную проблему?
Писать такой же говнокод, который обычно пишут абсолютно все любители redux, redux-thunk и прочей ереси. Ничего нового.
А в чём проблема нарезать стор/слайсы по модулям так, как вам удобнее? Библиотека этому никак не мешает, джаваскрипт у вас не отбирает. Или вы критикуете сам архитектурный паттерн с централизованным состоянием приложения? Да, он - не серебряная пуля, иногда бывает полезным, иногда лишним, зависит от вашего проекта. Иногда хватит дерева из визуальных компонентов с локальными состояниями (и независимыми походами в API), иногда потребуется централизованный стор для состояния приложения, а иногда помимо централизации потребуется и сложная оркестрация частей этого стора (см. mobX, rxJS, redux-saga). Выбирайте инструменты по задаче, а не по хайпу (культу карго) :).
Я критикую отсутствие инкапсуляции. В данной библиотеке кто угодно может изменить что угодно, что даже отражено в документации. Я считаю это большим источником проблем.
Посмотрим на код из примера:
const createBearSlice: StoreSlice<IBearSlice, IFishSlice> = (set, get) => ({
eatFish: () =>
set((prev) => ({ fishes: prev.fishes > 1 ? prev.fishes - 1 : 0 })),
sayHello: () => {
console.log(`hello ${get().fishes} fishes`);
},
});
const createFishSlice: StoreSlice<IFishSlice> = (set, get) => ({
fishes: 10,
});
Представьте что у нас таких едоков пол проекта. А потом мы решили добавить рядом с fishes
еще поле. Получается, нам надо найти все места, где стейт меняется и актуализировать.
Подход "всегда спредить стейт" или использование immerJS частично решит проблему, но не полностью, так как изменения могут сопровождаться побочными эффектами, которые тоже надо будет везде учитывать.
В общем, это все напоминает древнее процедурное программирование. Есть общий набор глобальных переменных, есть процедуры, которые как-то их меняют. Все остальное исключительно на совести программистов. От чего уходили, к тому и вернулись :)
Всё ещё не понимаю вашу проблему. Изолируйте логику так, как вам нужно, сами, библиотека не мешает вам. Но и не помогает ванговать вам ахритектуру вашего приложения (структуру вашего стора). Хотите - разделяйте, хотите - объединяйте. Не смог вас понять, видимо.
Если бизнес-логика изменилась - очевидно, придётся поменять все зависимые от неё части кода. Как иначе-то?
От чего уходили, к тому и вернулись
Возможно, вы этой библиотекой пытаетесь уйти от какой-то проблемы, к которой она не имеет отношения? :)
Как иначе-то?
Уменьшать число зависимостей, в том числе явно запрещая изменять "чужие" данные напрямую, а только через интерфейс. Т.е. я ожидаю увидеть что-то типа такого:
const createBearSlice: StoreSlice<IBearSlice, IFishSlice> = (set, get) => ({
bearFull: false,
eatFish: () => {
// fishes недоступно и менять нельзя, можно только вызвать метод:
fishSlice.fishDie();
// а свои поля менять можно:
set((prev) => ({ bearFull: true }));
},
sayHello: () => {
// получать "чужие" поля можно, но указав источник:
console.log(`hello ${get().fishSlice.fishes} fishes`);
},
});
const createFishSlice: StoreSlice<IFishSlice> = (set, get) => ({
fishes: 10,
// интерфейсный метод для снижения поголовья рыб:
fishDie: () =>
set((prev) => ({ fishes: prev.fishes > 1 ? prev.fishes - 1 : 0 })),
});
И кто вам мешает так сделать? Оверинжинирте свои типы и инкапсуляции как хотите, библиотека этому никак не мешает :).
Данная проблема не решается какой-то простой оберткой. Надо либо делать обертку create
, либо оборачивать каждый метод и бить по пальцам тех, кто так не делает. Ну или предложите способ, ибо я его не вижу)
библиотека этому никак не мешает
С таким подходом можно писать на голом JS, ибо он тем более не мешает сделать все что хочешь. Обычно от библиотеки мы хотим чтобы она решала наши проблемы, а не просто не мешала делать это самостоятельно.
Я конечно понимаю что разработческое садомазо вам явно доставляет удовольствие, но может все таки хватит терпеть боль и просто взять MobX? И все сразу как рукой снимет. Устроили демагогию вокруг очередной мертворожденной фигни, а потом приходи после вас на проекты и "наслаждайся" кровавым потоком из глаз глядя на ваши решенные проблемы с redux, redux-saga, recoil, rxjs, effecotor, zustand и прочей нелепой нечести. Зачем усложнять элементарные вещи на ровном месте, и вам будет легко жить и тем, кому ваше наследие будет доставаться.
Мне всё ещё не хватает квалификации понять вашу проблему, ретируюсь, пусть уже более опытные коллеги отвечают :). Возможно, вам поможет ссылка: https://github.com/pmndrs/zustand/blob/main/docs/typescript.md .
Кажется, я вас понял. Вы почему-то хотите использовать слайсы (кусочки логики одного стора, такова принципиальная идея этих сущностей) в качестве отдельных независимых сторов, чтобы экшны из одного слайса не имели доступа к другим слайсам, верно? Итак, решение: делайте отдельные сторы не слайсами, а… (барабанная дробь)… отдельными сторами! :)
Zustand — руководство по простому управлению состоянием