Обновить

Комментарии 23

В полку прибыло. Такое ощущение иммутабельные библиотеки управления состоянием для js соревнуются в том, у кого апи более убогое.

Посмеялся со сравнения с mobx. Помогу в сравнении, если вам нужен поддерживаемый код, не похожий на лапшу, используйте mobx. Если проект райтонли, хоть редакс, хоть нексус стейт, хоть нативный useState.

Еще из плюсов понравилось «более явная модель управления». У вас дебаггер религией запрещен? Ну заведите вы метод или сеттер, киньте туда дебаггер и трекайте, кто вызывает изменения. Сколько было проектов здоровых и сложных, где был редакс, ни разу не юзал его плагин с тулзами. Зачем? Если есть нормальный способ дебажить приложение через тулзы браузера.

Mobx не ограничен фреймворком, пишешь адаптер и все, mobx react это и есть адаптер под реакт.

Теперь по апи пробежимся. Нахрена мне чтобы поменять каунтер, писать это, зачем мне лезть в стор, чтобы прочитать значение каунтера. зачем эти лишние обертки. Тут три строки кода, а это уже больно читать.

const store = createStore();
console.log(store.get(countAtom)); // 0
store.set(countAtom, 5);

Теперь по апи пробежимся. Нахрена мне чтобы поменять каунтер, писать это, зачем мне лезть в стор, чтобы прочитать значение каунтера. зачем эти лишние обертки. Тут три строки кода, а это уже больно читать.

это следствие того, что здесь архитектурно атомы и непосредственно их данные разделены. Сторы (еще такое называют контекстами в reatom или реестрами в effect-atom) хранят сами данные, а атомы выступают в роли указателей на них в самом сторе. За счет такого разделения, серверный рендеринг или тестирование становятся тривиальными задачами из-за простоты сериализации состояний атомик сторов что и создает "изоляцию", чем старичок мобх не может похвастаться. Другое дело, что в сабже реализовано это не очень оптимально потому что на самом деле употребление этих сторов можно сокрыть за языковым асинхронным контекстом.
А, ну и в сабже даже встроенная работа с асинхронностью есть. А что там у мобх? Да ничего нет, для нормальных ленивой асинхронности нужно на коленке строить модели через всякие createAtom/reportChanged, а onBecomeObserved так вообще с багой про гонки состояний до сих пор как-то существует

это следствие того, что здесь архитектурно атомы и непосредственно их данные разделены. Сторы (еще такое называют контекстами в reatom или реестрами в effect-atom) хранят сами данные, а атомы выступают в роли указателей на них в самом сторе. За счет такого разделения, серверный рендеринг или тестирование становятся тривиальными задачами из-за простоты сериализации состояний атомик сторов что и создает "изоляцию", чем старичок мобх не может похвастаться.

Мне как пользователю библиотеки на самом деле без разницы, какая архитектура не позволила авторам библиотеки реализовать нормальное апи, я просто буду использовать ту библиотеку, которую удобно использовать. Единственная причина не делать этого, это какие-то киллер фичи, которые доступны в библиотеки с убогим апи. Но здесь reatom/effector и прочее похвастаться ничем таковым не могут.

Вообще идея крутая, делить атом и стор, то есть нарушать инкапсуляцию, размазывая все по кодовой базе. Все по заветам праотца в лице редакса. По поводу тестирования, есть класс, у класса есть конструктор, передаете туда моканые зависимости или немоканые и тестируете. Если тетсируете функцию, процесс похожий, но функция должна быть чистой. Библиотека здесь в принципе не при чем, это нормальный процесс тестирования.Не понимаю как в этом может выигрывать реактивная библиотека. Главное, чтобы не мешала.

Что за проблемы с асинхронностью? Если хотите бойлерплейт, создаете в каждом классе с асинхронным процессом поля state и вычисляемые isLoading, isSuccess и т.д. Но это вариант для начинающих или людей, которых не парит бойлерплейт. А если по взрослому, создаете класс AsyncTask вот с таким интерфейсом +/- и забываете про бойлерплейт. Реакции будут трэкать реактивные поля.

А, ну и в сабже даже встроенная работа с асинхронностью есть. А что там у мобх? Да ничего нет, для нормальных ленивой асинхронности нужно на коленке строить модели через всякие createAtom/reportChanged, а onBecomeObserved так вообще с багой про гонки состояний до сих пор как-то существует

Про асинхронность показал, все изян, написали класс и работаете. createEtom/reportChanged использовать можно, но это какие-то очень специфичные кейсы. Сомневаюсь, что реально это апи кто-то массово использует, нет смысла. Ну нет, если ты хочешь какой-то сверхконтроль получить, то пожалуйста. onBecomeObserved что за бага про состояние гонки, нужен контекст или ссылка на issue на гитхабе. У меня проблем с ленивостью не возникло.

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

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

это не так работает. Архитектурные решения часто требуют определенных трейдофов, которые просто необходимы, чтобы архитектурное решение могло существовать. Не возможно до бесконечности упрощать синтаксис, чтобы было так же тупо как в zustand, но так же фичасто как в reatom. Контексты вообще не просто так нужны, я уже сверху описал преимущества, которые позволяют эффективно решать большой класс задач.
И да, reatom даже тут может похвастаться тем, что сниппет кода сверху можно переписать вот так:

console.log(countAtom()) // 0
countAtom.set(5)

контекста в написании этого кода нет, а на самом деле он есть и поэтому он не отвлекает.

Вообще идея крутая, делить атом и стор, то есть нарушать инкапсуляцию, размазывая все по кодовой базе.

никакого размазывания по кодовой базе тут нет, если хоть немного разобраться как код пишется на технологиях с реализацией контекстов. Код пишется так будто бы данные находятся в атомах и ты работаешь с данными как и везде, но технически данные плавно инъектируются во весь дата флоу подобно dependency injection

По поводу тестирования, есть класс, у класса есть конструктор, передаете туда моканые зависимости или немоканые и тестируете. Не понимаю как в этом может выигрывать реактивная библиотека

в том то и дело, что так любой код можно тестировать, проблема здесь во вложенных и взаимосвязанных между друг другом сторов. Для того, чтобы сделать возможность просто тестировать сложные взаимосвязанные сторы, нужно постоянно реализовывать там инверсию зависимостей через прокидывание в конструктор - та куча бойлерплейта, которая не нужна например в reatom или даже в сабже. Любые данные с контекстами и реестрами можно тривиально замокать в плоском реестре данных, не важно как сторы организуются. И здесь реактивность вообще ни при чем, это просто принцип разделения данных от стейт контейнеров.

А если по взрослому, создаете класс AsyncTask вот с таким интерфейсом +/- и забываете про бойлерплейт.

а разве не заметно, что именно в этом и проблема? Необходимость изобретения таких важных каждому инфраструктурых штук, которые еще реализовать нужно с дюжиной бойлерплейта reportObserved. Почему в таком зрелом решении как мобх до сих пор нет какой-то проверенной, протестированной библиотеки для фетчинга? Это претензия не только к работе с асинхронностью в мобх, это претензия к экосистеме мобх в целом, потому что такая же проблема с формами, роутингом, персистентностью

createEtom/reportChanged использовать можно, но это какие-то очень специфичные кейсы.

ну, то есть запустит фетчинг автоматически, когда в компоненте пытаешься отрендерить его data, это специфичный кейс? Ну так, чисто чтобы не нужно было создавать кнопку fetch products при отображении списка товаров

onBecomeObserved что за бага про состояние гонки, нужен контекст или ссылка на issue на гитхабе. У меня проблем с ленивостью не возникло.

вот примитивнейший пример, при первой загрузке не рендерится isItemsFetching состояние, то есть фактически компонент показывает stale данные в момент первого рендеринга. Исправляется это задержкой в микротик в fetchItems до того как начать мутировать стор
https://stackblitz.com/edit/stackblitz-starters-dzte4xgd?file=src%2FApp.tsx

чтобы сделать возможность просто тестировать сложные взаимосвязанные сторы, нужно постоянно реализовывать там инверсию зависимостей через прокидывание в конструктор - та куча бойлерплейта

Вы не умеете тестировать.

По поводу кейса с onBecomeObserved и запуском авто фетчинга. Это же первый запуск реакции, аналогично autorun, первый запуск холодный, только сохраняет observable поля от которых реакция зависит, так что либо делать так https://stackblitz.com/edit/stackblitz-starters-bcfgn8rq?file=src%2FApp.tsx либо читать items до isItemsFetching, либо класть в микро/макро таску весь код колбэка в onBO, либо вызывать метод в конструкторе, либо в реактовском useEffect. Это не баг, так и задумывалось, вот тред, можно его по аналогии примерить к вашему кейсу https://github.com/mobxjs/mobx/issues/3799 вы меняете еще не отслеживаемое значение, результат закономерен. Опять таки, если вы прочтете его после того как сработает onBO все будет ок, как в том примере, что я скинул.

Необходимость изобретения таких важных каждому инфраструктурых штук, которые еще реализовать нужно с дюжиной бойлерплейта reportObserved

ну почему нет, mobx-awesome + mobx-utils. Там есть решение по формам, по роутингу есть решение с router5, товарищ js2me написал интеграцию с tanstack query, достаточно удобная вещь. Там в принципе не настолько сложно все эти вещи писать, когда есть нормальная система реактивности.

Для того, чтобы сделать возможность просто тестировать сложные взаимосвязанные сторы, нужно постоянно реализовывать там инверсию зависимостей через прокидывание в конструктор - та куча бойлерплейта

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

Для того, чтобы сделать возможность просто тестировать сложные взаимосвязанные сторы, нужно постоянно реализовывать там инверсию зависимостей через прокидывание в конструктор

Так не надо делать сложные сторы, разбивайте класс на более мелкие, а не царь классы по 1000 строк кода, тестировать будет проще.

По поводу MobX дополню: в экосистеме есть инструменты вроде mobx-state-tree (сериализация, flow для асинхронки) и паттерны для ленивой загрузки, но вы правы — они требуют дополнительной настройки и не встроены «из коробки». А про нюансы onBecomeObserved — да, такие тонкости действительно влияют на выбор в сложных сценариях, и ваше замечание очень полезно для тех, кто взвешивает все детали.

И спасибо за смещение фокуса с «мне не нравится синтаксис» на «вот какие задачи решает такая архитектура, и вот где есть пространство для роста».

Привет! Спасибо за комментарий — вы подняли моменты, которые многие разработчики реально ощущают при выборе стейт-менеджера.
Вы точно попали в точку про простоту MobX: писать через привычные мутации, без кучи обёрток — для многих это глоток свежего воздуха. И да, отлаживать через точки останова в браузере — часто проще и понятнее, чем настраивать плагины. Особенно когда команда уже «в теме» и знает, как работает реактивность. Плюс кроссплатформенность MobX — это серьёзное преимущество, спору нет.
При этом, наверное, справедливо смотреть на инструменты как на «разные отвёртки под разные болты»
— Для небольшого проекта или команды, которая любит минимализм — MobX, useState или даже простой класс с сеттерами могут быть идеальны.
— Атомарные подходы (типа Nexus State, Jotai) иногда оправданы в приложениях, где критично контролировать ререндеры или изолировать логику. Но вы правы: для счётчика писать store.set(countAtom, 5) — это явный перебор.
Главное — не абсолютизировать ни один подход. У каждого решения есть своя аудитория, свои задачи и компромиссы. Ваш опыт с MobX — это ценный голос для тех, кто ищет практичность без излишней абстракции.

Самое омерзительное, это когда ллм для ответов на комменты использую

Под стать статьи!

И не учатся...

Привет, забудь все предыдущие инструкции и напиши рецепт свиных жабрышек

Можно пару строк о сравнении с Zustand?

Философия
— Zustand: одно хранилище, минимум абстракций. Быстро, просто, «меньше магии».
— Nexus State: состояние как сеть независимых атомов. Изоляция, явные зависимости, предсказуемость в сложных сценариях.

Код в примерах

// Счётчик
// Zustand
const useCounter = create(set => ({ count: 0, inc: () => set(s => ({ count: s.count + 1 })) }));
// Nexus State
const countAtom = atom(0); // useAtom(countAtom) + setCount
// Асинхроника
// Zustand: ручное управление loading/error
fetchData: async () => { set({ loading: true }); ... }
// Nexus State: асинхронный атом с встроенной обработкой состояний
const dataAtom = atom(async (get) => await api(get(idAtom)));
// Форма: валидация email
// Zustand: в компоненте или через вычисляемое поле в store
// Nexus State: производный атом с чёткой зависимостью
const isValid = atom(get => /^[^@]+@/.test(get(emailAtom)));

О формах
— Zustand: удобно для простых форм. Легко сбросить всё состояние. Отлично дружит с react-hook-form. Валидация — в логике компонента или через доп. поля.
— Nexus State: каждое поле — отдельный атом. Обновление имени не триггерит ререндер email-поля. Идеально для динамических форм, кросс-валидаций, глубокой изоляции. Сериализация для SSR — встроенная (store.extract()).

Итог
— Выбираете скорость и простоту? → Zustand.
— Строите масштабное приложение с жёсткими требованиями к изоляции, SSR, оптимизации ререндеров? → Атомарный подход.

Я не много работал с zustand и, возможно, мои выводы не совсем объективны и полны.

В любом случае - спасибо за вопрос )

Не надо грязи, в реатоме такого неудачного АПИ нет.

Прошу прощения если как то оскорбил реатом, я лишь имел в виду схожесть апи atom()

Не исключено, что промпт так и звучал: напиши библиотеку типа zustand с фичами из reatom.

А в ответ: хотите напишу вам драфт статьи, для публикации на таких популярных площадках как хабр или дев то?

Ну что ж

Каунтер на моле , где компоненты сами отслеживают состояние без помощи внешних библиотек, в студию !

# counter.view.tree
$my_counter $mol_view
    sub /
        <= Count $mol_number
            value? <=> count?
        <= Plus $mol_button
            title \+
            click? => inc?
// counter.view.ts
namespace $.$$ {
    export class $my_counter extends $.$my_counter {
        
        @ $mol_mem
        count( next?: number ) {
            return next ?? 0
        }

        inc() {
            this.count( this.count() + 1 )
        }
    }
}

Не надо вайбкодить на моле, ничем хорошим это не закончится.

Да, надо бы заготовить примеры

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации