Pull to refresh

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 МБ по умолчанию настроек клиента.

Медленнее оперативной памяти да, но достаточно быстрый для использования в любых кейсах. Короче все равно быстрый.

5Mb это очень много.

indexed DB ограничивает размер % от свободного места на диске.

Хах, классика, идет сравнение с убогими "соперниками" в виде 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 и прочей нелепой нечести. Зачем усложнять элементарные вещи на ровном месте, и вам будет легко жить и тем, кому ваше наследие будет доставаться.

Ваши привычки и ваш синдром утёнка, конечно, более правильные и важные, чем привычки других людей ;).

Почему именно утенка? Это может быть выбор на основании практического опыта.

Все мы опытные утята, евангелизирующие свои убеждения ;)

Кажется, я вас понял. Вы почему-то хотите использовать слайсы (кусочки логики одного стора, такова принципиальная идея этих сущностей) в качестве отдельных независимых сторов, чтобы экшны из одного слайса не имели доступа к другим слайсам, верно? Итак, решение: делайте отдельные сторы не слайсами, а… (барабанная дробь)… отдельными сторами! :)

Sign up to leave a comment.

Articles