Pull to refresh

Comments 478

Мы в своё время в Медиасервисах Яндекса перевели два приложения с Redux на MobX — скорость разработки увеличилась раза в три. Сейчас делаем тоже самое в UBS. Вообще в официальной документации Redux когда-то было написано, что он подходит для примерно 10% приложений. При этом компания-автор, Facebook, в это число не входит. Но хайп же важнее логики, как обычно.

А хотите ещё в 3 раза увеличить скорость разработки — выпиливайте React :-)

Тогда давай отказываться и от типизации, стандартов, соглашений и многого другого, что занимает время.
Когда попадаются проекты, на том же реакте, где был сделан упор на скорость, где не используется redux и другие библиотеки получается что то вот такое: github.com/magickasoft/wacdaq/blob/master/src/components/pagesRouter/pagesRouter.jsx

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

Ух ты. Спасибо за ссылку. Такое надо в закладки.

Вчера проходил собеседование, типичная ситуация, проект полу дохлый:
1. Плохая структура проекта, все сложено в папку components, нет разделение на компоненты и контейнеры, тяжело разобраться, какая страница отображает какие компоненты, полный хаос

2. Вся логика в роутере, он же и есть данные + бизнес логика приложения, как это поддерживать?

3. Полная загрузка страницы занимает около 40 секунд, при этом ресурсов загружается на 20мб, это очень много

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

Классика жанра) к сожалению

Реакт имеет архитектуру вида — перезапускаем рендер всего приложения и где только можно срезаем углы, чтобы это не тормозило. МобХ же отслеживает зависимости между состояниями и точечно их обновляет. Реакт для МобХ — как собаке пятая нога. А если хочется JSX, то кастомный реактивный рендерер займёт всего несколько сотен строк.

Я как-то пытался так и сделать, в итоге все равно Реакт получился. Проблема в самом JSX: если у нас по условию есть функция вида () => JsxFragment, то мы обязаны отслеживать любые ее зависимости и выполнять повторные рендеры с реконциляцией при их обновлениях.


Можно, конечно, придумать решения для биндинга выражений на свойства, и это будет куда оптимальнее… вот только одно случайно допущенное обращение к observable — и мы снова перед выбором, оставить программу глючить или сделать повторный рендер с реконциляцией как в Реакте.

Не очень понял вашу проблему, но вместо JsxFragment лучше возвращать DOMElement. Поскольку он каждый раз будет возвращаться один и тот же — инвалидация выше по дереву идти не будет.

А не получится его каждый раз один и тот же возвращать.


Только не начинайте снова про "просто добавим уникальный id каждому тегу". Это не защищено от дурака и от глупых опечаток, и просто неудобно.

Просто наберитесь опыта в реакте и такие вопросы, проблемы и желания у вас врятли вновь появятся

От опечаток защитить не сложно.
И наличие человекопонятного идентификатора у каждого элемента — это дико удобно, на самом деле. У них просто тьма полезных применений.

Как гарантировать уникальность и читаемость одновременно?

Алгоритм примерно такой:


  1. При старте рендеринга в глобальные переменные запоминается текущий guid и пустое множество для локальных имён.
  2. Для каждого тега проверяется есть ли указанный в пропсах id. И если есть, то кидается ошибка.
  3. guid из глобальной переменной склеивается с локальным id — получается guid для конкретного тега.
  4. Если в доме есть элемент с аттрибутом id = guid, то берётся он, иначе создаётся новый и ему проставляется этот аттрибут.

То есть только в рантайме и только когда дойдёт очередь до конкретного рендеринга? Причём где допущена ошибка понятно не будет, выскочить на том компоненте, который рендерится вторым по флоу приложения, а не на том, который был создан вторым по флоу разработки?

Если у вас есть решение лучше — я весь во внимании.

Нет. Необходимость присваивать всем элементам id — это не удобно.

Эта необходимость есть в любом случае для проекта сложнее "привет, мир". Разница лишь в том, насколько удобно это делать.

Скажите, а как? Я ни разу не фронтендер (ладно, я был джуном, но завязал), потребовалось тут сваять что-то не очень сложное, но весьма интерактивное. Наткнулся на эту статью как раз в поисках того, на чем пилить. Если вы советуете выпилить реакт — я с радостью приму ваши советы. Надеюсь, это не jquery, ибо портянки.

Советую вам изучить HTML5 и пользоваться MDN как библией. А потом уже решать что на чем писать.

HTML, CSS и даже JS я и так знаю. Но писать все на pure js: обработчиках onclick ручном добавлении/удалении тегов нет никакого желания.

А зачем вам тогда что то изучать, раз вы и так все знаете

Я хочу это развидеть)

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

Не нахожу зависимости между SSR и redux. Возможно redux+SSR просто описан в документации. Общий стор можно реализовать хоть бы и MobX А можно вообще заюзать graphql+apollo и забыть про стор.
Делал на riotjs + effectorjs см. habr.com/ru/post/456448
Кстати делал на основе другого проекта reactjs + redux выпиливанием 2/3 кода стора. Перед тем как взял в работу effectorjs процентов 10 сделал на mobx. Конечно у riotjs более открый интерфейс в то время как в react даже redux ходил по недокументированная ранее api.

Собственно, мой вопрос заключается в следующем: для полноценного SSR необходимо грузить стейт на сервере, рендерить страницу, а потом стейт сериализировать в страницу (в тег script обычно).


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


Поддерживают ли модели MobX сериализацию?

Да об этом я не подумал у effectorjs есть из коробки у mobx сложнее но в доках есть описание mobx.js.org/refguide/tojson.html с сериализацией все вроде бы просто а вот десериал щация идёт в отдельном проекте

Это зачем такие извращения? Сср нужен лишь для роботов, что не умеют в яваскрипт.

Это вопрос с запутанной историей. Ява скрипт умеет скорее всего только google во всяком случае в больших обьемах. Там не менее в доках инфа об этом ограничивалась ответом в интервью одного из сотрудников google, который ответил на вопрос примерно так: можем ли мы парсить яваскрипт? а почему бы и нет?
Потом где то год назад появились гайды для веб разработчиков где была показана схема с prerender. То есть как бы подтверждение того что читать они могут но скорее всего не хватало мощностей чтобы делать это для всех сайтов без исключения. И вот теперь недавно появилась серия интервью почему то с распитием коньяка где пропагандируется отказ от ssr и prerender. Я честно говоря воспринял это как то что google теперь гарантированно и 100% читает яваскрипт и решил наконец окончательно стать единственной поисковой системой тк другим для чтения яваскрипт придется на два порядка увеличить мощности по железу что не все поисковики выдержат финансово.
Но пока ещё пользователи ищут не только на google. Кроме этого яваскрипт может быть тяжел для работы на слабых девайсах и не читаться скринридерами.

Хотя я сталкиваюсь с игнорированием этих вопросов разработчиками. т.к. хочу ангуляр/реакт/вью и не хочу ничего больше знать и ни о чем больше думать.
UFO just landed and posted this here
Там в комментариях ниже рекомендуют для этого использовать mobx-state-tree

А есть примеры использования graphql + Apollo вместо redux или стора в принципе?

Идея с единым местом, хранящим всё состояние приложения, очень помогает при разработке изоморфных приложений. На mobx можно сделать подобное, с помощью mobx-state-tree, который здесь уже посоветовали, но тогда вы получаете почти тот же самый редакс.

При чем тут state-tree? Просто mobx тебе позволяет делать единое место хранения и ещё много всего, никак не ограничивая тебя и не связывая руки.

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

Я напишу статью когда вернусь из отпуска, с телефона это будет сделать нереально. А вообще я писал уже комментарий по этому поводу с подсказками, что надо сделать чтобы работало так, как вы хотите. Можете его прочитать и ветку с этим комментарием и может у вас все встанет на свои места если уже юзали mobx

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

А какой вам при SSR нужен стейт кроме, с-но, самого роута?

Данные которые дёргаются от АПИ, что бы отрендерить например страницу со списком товаров

Данные которые дёргаются от АПИ, что бы отрендерить например страницу со списком товаров

Ну так дергаете и рендерите, в чем там проблема и как с ней связано mobx/redux?

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

Не понял, зачем? состояние в роуте, с-но просто рендерим с роута, как обычно. Где проблема возникает?

Код должен быть единым на клиенте и на сервере. Если я перейду на страницу с категориями товаров то скорее на клиенте будет вызван метод например getCategiries который загрузит с сервера данные в стор и у меня на странице появится списков категорий. Теперь я нажимаю клавишу F5 и загружаю страницу с сервера. На сервере чтобы мой компонент отрендерился с категориями нужно инициализировать стор со списком категорий. Тк это тот же самый компонент который работал и на клиенте.
Если реакт использовать для чисто серверного рендеринга где все страницы рендерится на сервере то сторинеинужен

А так ли нужен этот серверный рендеринг?

Только если seo оптимизация критична, у меня тьфу тьфу таких проектов на реакте не было и не будет (обычно это интернет магазины и прочее не интересеое)

Я вам по секрету скажу что для роботов можно выдавать HTML без JS, а для человеков JS без HTML. И нет, вас не забанят поисковики — можете погуглить их рекомендации по индексированию js-приложений.

Теперь я нажимаю клавишу F5 и загружаю страницу с сервера.

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

На клиенте getCategiries грузит данные в стор. Если функцию вызвать на сервере она также сделает то же самое что и на клиенте то есть загрузит данные в стор. Стор передается на сервере компоненту для его серверного рендеринга. После этого стор должен быть скриализован и отправлен на клиент чтобы на клиенте повторно не вызывать метод getCategories. На клиенте стор десериализуктся и передается в компонент при его гидратации (hydrate())
Проблема которая послужила началом этого треда состояла в том что mobx не имеет встроенного метода для десериализации объекта и как я понял по коду примеров с сериализацией там тоже не все хорошо в части циклических обьектов

Можно просто написать свое решение и все, там на самом деле все достаточно просто, подсказка: сериализатор и десириализатор + функция которая будет вызываться в конструкторе каждого класса mobx которая будет восстанавливать его состояние и будет счастье с минимальным вмешательством в архитектуру и в целом в код

Если реакт использовать для чисто серверного рендеринга где все страницы рендерится на сервере

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

А вот в том же Razor (asp.net) есть не только поддержка типов — но и поддержка метамодели, которая позволяет провести как клиентскую валидацию, так и серверную, на основе атрибутов (=аннотаций) свойств.


И для простых CRUD эту же валидацию можно частично продублировать на стороне СУБД средствами Entity Framework.

В Play — тоже, но даже для Java это редкое поведение
То что написал apapacy и есть то, что подразумевается под «react ssr». Первый запрос рендерится с сервера, всё остальное уже на клиенте. В данном случае, необходимо иметь полный отпечаток состояния приложения независимо от того, выполняется код нодой или браузером.
UFO just landed and posted this here
Согласен, много шаблонного кода, но если использовать redux-act все становится немного удобнее и к тому же, где находится логика приложения? Вместе с данными? Мне больше нравится использовать Redux-Saga, данные отдельно, логика отдельно.

Вообще как удобно так и можешь делать, никаких ограничений и навязывания что и как надо делать

Логику можно разделить на логику отображения и логику обработки данных. Логику отображения храните в компонентах React, логику обработки данных вместе с данными, которые эта логика обрабатывает. Никаких минусов такого подхода за долгие годы не обнаружил, наоборот фичи получаются более локализованные.
Согласен, такой подход позволяет убрать всю бизнес логику из компонентов, оставить в них только логику отображения. Тут тоже несколько плюсов:
1. Компоненты становятся меньше и соответственно более понятно, что в них происходит
2. Если в компонентах фигачить бизнес-логику, она будет пересчитываться каждый раз, когда этот компонент будет перерендериваться, а это минус к производительности
3. Компоненты становятся менее зависимыми от данных == больше шанс их переиспользовать.
Можно создать отдельный класс в котором будут данные и отдельный класс в котором будут методы. Лично я предпочитаю хранить и данные и методы для этих данных в одном классе:
class Users {
   @observable list = [];
   @action getUser = (id) =>{
       return this.usersList.filter(user => return {user.id == id})
   }
   @action fetchList = () => {
       return new Promise((resolve, reject) => 
                api.get('someUrl')
                    .then(res=> {
                          this.list = res.data;
                          resolve()
                     })
                   .catch(e => reject(e))
       )
   }
}

Такой подход позволяет не просто хранить данные, а создавать сущности и потом в компоненте сразу понятно, что происходит:
try {
    await Users.fetchList();
    Users.getUser(id);
} catch(e){
    console.log(e)
}
Я вот не сторонник объединения разных обязанностей в одном классе.
Для меня норм, когда стор хранит данные и отвечает за их преобразование при обращении к нему. На мой взгляд, не стоит совмещать в сторе работу с данными и отправку запросов на сервер.

Единого правильного пути нет, кому как удобнее и кто как считает более приемлемым

Недавно попал на поддержку проект абсолютно правильный. И это тоже иногда бывает не то что бы плохо а неудобно. Например обычный restapi put user. Тут и в роуте можно сохраниться в базу данных и забыть. Но это слишком просто. В роуте мы вызываем контроллер saveUser. ОК тут бы и остановиться. Но нет из контроллера мы вызываем сервис saveUser. Классно уже кажется хватит. Но нет из сервиса мы делаем метод репозитория saveUser. И тут уж — опять нет мы идём в прослойку между базой и всем что было до того которая почему то называется DTO. И здесь то у мы наконец сохраняемая.

Вроде бы и не поспоришь. А начнёшь спорить то будешь уличен в нарушении SOLID. Но как то все это подозрительно из простой задачи превратилось в какие то джунгли

Как я тебя понимаю, к великому сожалению такая жесть преобладает

А начнёшь спорить то будешь уличен в нарушении SOLID.

Это нарушение не SOLID, а квадратно-гнездового принципа. В реальном приложении у Вам в каждом из модулей будет нужно выделить отдельно либо контроллер, либо какой-то сервис, либо репозиторий. Проблема в том что, если вы будете выделять в каждом из модулей только нужное у Вас получатся сильно разнородные модули.

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

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

Возможно Вы думаете, что большинство таких контроллеров/сервисов/репозиториев — не занятых ничем кроме проброса значения дальше, это какое-то отражение внутренней логики. Нет, это тупо форматирование. Как скобочки — форматирование для файла, так и эти тривиальные классы — форматирование для проекта. Возможно ху… е, возможно стоило сделать нормальное дефолтное поведение, но главно это одинаковое на всем проекте.

Главное, чтобы реально было одинаково, чтобы каждый чётко понимал ответственность каждого слоя.

Когда я в 2012м увидил такой подход в Backbone, он у меня тоже вызвал протест. Но на практике оказалось, что связывать серверные данные и методы работы с сервером не такая плохая идея. Возможно потому что часто между ними есть логическая сильная связанность, и это прощает повторение этой связанности в коде. Но это не определяет использование ни MobX, ни Redux, ни чего-либо ещё. Просто Redux физически не позволяет так сделать, а MobX оставляет выбор за вами. Разделять или объединять (DDD или CQRS) — зависит от природы данных, требований и слишком многих других факторов.

Лучше так, как вы написали, не делать...


Во-первых, при наличии таких операций как getUser, элементы хранить надо в Map, а не в массиве. При использовании MobX это особенно важно, ведь от структуры данных зависит какие будут подписки: к примеру, сейчас у вас каждый вызов getUser подписывается на нулевой элемент массива.


Во-вторых, если fetchList — единственная мутирующая операция, то имеет смысл использовать observable.ref вместо observable.


В-третьих, откуда вы взяли этот паттерн с оборачиванием Promise в другой Promise?! Зачем вообще внешний Promise нужен?


В-четвертых, action не действует на асинхронные продолжения. Конкретно в приведенном вами коде декоратор action бессмысленный.

Во-первых, при наличии таких операций как getUser, элементы хранить надо в Map, а не в массиве.

Не монимаю, почему в данном примере надо использовать map. Я этот массив не собираюсь никак менять.

Во-вторых, если fetchList — единственная мутирующая операция, то имеет смысл использовать observable.ref вместо observable.

С этим согласен

В-третьих, откуда вы взяли этот паттерн с оборачиванием Promise в другой Promise?! Зачем вообще внешний Promise нужен?


Конкретно в данном случае это не нужно, писал в спешке. Такую конструкцию Promise в Promise, я обычно использую когда неизвестно, какой статус от сервера может придти. Это может быть 200 или 400. И то и то нормально. Но если я потом буду ждать ответ в async он упадет, а в случае с двумя промисами у меня есть возможность прописать resolve даже если промис вернется не со статусом ОК.

В-четвертых, action не действует на асинхронные продолжения. Конкретно в приведенном вами коде декоратор action бессмысленный.


Оф документация:

The action wrapper / decorator only affects the currently running function, not functions that are scheduled (but not invoked) by the current function! This means that if you have a setTimeout, promise.then or async construction, and in that callback some more state is changed, those callbacks should be wrapped in action as well!

Не монимаю, почему в данном примере надо использовать map. Я этот массив не собираюсь никак менять.

Вопрос в операции поиска. Посмотрите внимательнее сколько зависимостей создает операция filter...


Но если я потом буду ждать ответ в async он упадет, а в случае с двумя промисами у меня есть возможность прописать resolve даже если промис вернется не со статусом ОК.

Но вы можете просто вернуть значение из catch, с аналогичным эффектом.


Оборачивание промиса в промис не требуется никогда.


The action wrapper / decorator only affects the currently running function, not functions that are scheduled (but not invoked) by the current function! This means that if you have a setTimeout, promise.then or async construction, and in that callback some more state is changed, those callbacks should be wrapped in action as well!

Ага, а вы этого (those callbacks should be wrapped in action) не сделали.

Юзаю MobX с 2017 года, после Redux и его "друзей" это просто идеально, да и вообще это серебряная пуля в связке с реактом решает 101% всех задач быстро, эффективно и главное код сохраняет читаемость по мере роста и развития проекта. А ещё шикарнейшая плюшка — он позволяет забыть о this.setState для внутреннего состояния компонента

Вся асинхронная логика может реализоваться на чистом Redux через разные уровни Middleware.

MobX также увеличивает кривую обучения через Observables, которые так и не стали частью языка.

На своем пути к минимализму я остановился на связке useReducer() hook + Context, которые идут «из коробки» и успешно заменяют мне 90% функциональности Redux.

Уделить 1-2 часа доке MobX, react-mobx и поиграться с ними и все, считай что ты ас и не надо никаких хуков и прочей нечисти, чистые функции так и останутся чистыми.

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

Вся асинхронная логика может реализоваться на чистом Redux через разные уровни Middleware.

Я вам открою секрет — на чистом Redux через middleware можно вообще реализовать интерпретатор любого ЯП.

RxJX по мне используют самые отмороженные извращенцы с реактом

Нет, самые отмороженные используют Angular с NgRx.

Просто взгляни на этот код и потом разматывай клубок чтобы понять че к чему, естественно если этот код написал не ты сам или прошло с момента его написания скажем больше 2х недель. Async/await с 2017 года официально поддерживается, какой нафиг RxJS для реакта, там и так уже давно идеальная эко система в связке с mobx и aync/await

Async/await с 2017 года официально поддерживается, какой нафиг RxJS

Какая связь между async/await и rxjs? Это совершенно разные инструменты для решения совершенно разных задач.

Какую задачу решает rxjs что не может решить es2019, реаки и mobx?

Так я и думал) ответа не будет

Какую задачу решает rxjs что не может решить es2019, реаки и mobx?

Какую задачу решает es2019, реакт и mobx, которую не может решить брейнфак или любой другой тьюринг-полный ЯП?

NgRx — это гремучая смесь идей Redux/Flux, реализованных на основе RxJS. Все это щедро приправлено обязательной хардкорной типизацией TypeScript и кучей индексных файлов с «import * from». Все это с большим скрипом ложится на архитектуру Angular и, само собой, только с Angular и работает.

Оригинальный Redux по сравнению с NgRX — образец минимализма и изящества, даже если заморочиться с TypeScript.
Оригинальный Redux по сравнению с NgRX — образец минимализма и изящества, даже если заморочиться с TypeScript.

На самом деле — нет, NgRX значительно проще, концептуально стройнее и изящнее голого Redux'a. Во многом за счет использования rxjs и тайпскрипта как раз.

Что, если впринципе не использовать state management?

Нееет, ну что вы. А проблемы? Об чем писать статьи в духе "грабли redux state роутера или как мы ускорили приложение в 1.5 раза переписав все на классы с поддержкой нового бандлинга вебпак". Кому это нужно то? У нас же все тут пишут дико крутые проекты с миллиардами пользователей где без реакта никак ну вообще никак не обойтись. Я не понимаю как вообще мы без него жили до этого?

Ну нет. Попробуйте написать SPA с 20 типичными юзкейсами на jQuery, потом обсудим, как там клёво на коллбэках.

Я так понимаю, идея-то заключалась в том, чтобы не писать SPA :-)

Все развалится и будет ад?

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

Причем тут императивный подход вообще??

По поводу императивного подхода мы действительно мыслим слишком императивно. Поэтому везде где можно сразу начинаем думать а как здесь реализовать экшины. Но есть и другой подход. Например связка react+react router v 4+apollo+graphql. Имеем декларативное описание данных, роутов и все это очень упрощает архитектуру фронтенда

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

Почему же неочевилным. Как раз более очевидным. Т.к. запрос тут же рядом с описанием компонента например:

import React from 'react';
import { Link } from 'react-router-dom'
import { Query } from 'react-apollo';
import gql from 'graphql-tag';

const Post = (props) => (
  <Query
    query={gql`
      query {
        Post(id: "${props.match.params.postId}") {
          id
          title
          text
        }
      }
   `}
    fetchPolicy='network-only'
  >
    {({ loading, error, data }) => {
      if (loading) return <p>Loading...</p>;
      if (error) return <p>Error :(</p>;
      return (
        <ul key='topPosts'>
          <li>{data.Post.id}</li>
          <li>{data.Post.title}</li>
          <li>{data.Post.text}</li>
        </ul>
      );
    }}
  </Query>
);

export default Post;

В данном примере очевидно, но глаза режет) как по мне так запросы надо выносить в отдельную функцию и тем более не использовать это в функциональном-компоненте, он должны быть чистым и простым. Для всего остального есть класс с человеческим жизненным циклом

Т.к. запрос тут же рядом с описанием компонента например:

Запросы в верстке tt.

Когда я впервые познакомился с текстом было недоумение js + html все вместе это же m+v+c сразу. Но почему тогда это работает и даже лучше чем bb (Backbone) где все по фэншую.
В данном примере есть декларативное описание данных но нет самой логики. Поэтому это не модель. Есть декларативное описание ноута но нет действия поэтому это не контроллер. В общем то наверное это и не Вид, т.к. это все не модель mvc, а такое себе декларативное описание компонента

Но почему тогда это работает и даже лучше чем bb (Backbone) где все по фэншую.

Потому что не лучше. Это работает, пока у вас маленькие простые write-only проекты вроде фейсбука, у которых цикл разработки год-два, сапортить ничего не надо, а если вдруг что — можно просто выкинуть и переписать с нуля, без особых потерь.
Такие проекты можно писать на чем угодно, и оно будет работать.
Точно так же раньше работал write-only код на php, пока сайты не стали сложными, и с поддержкой говнокода не появились проблемы.

Использую reduxsauce — количество кода сократилось заметно, декларативное описание action generators, все дела. Тоже вполне себе вариант.
Еще недавно React.PureComponent банально не поддерживался. С версии mobx-react 6.0.0 их рекомендуется использовать и в mobx. А вообще использую в связке с mobx-state-tree.

К слову после того как я увидел mobx-state-tree больше ничего другого использовать не хочется. Redux, как мне кажется, используется по инерции, а также из-за большого количества готовых разработчиков под него.

У меня с mobx-state-tree есть проблема — автокомплит в WebStorm. Для себя я решил это написав на коленке тулзу для генерации JSDoc. А как вы с этим справляетесь?

Ниже вам уже ответили, переходите на тайп, не представляю как вообще раньше у меня хоть что-то получалось без дженериков и интерфейсов. Сложно только на стадии создания архитектуры, сама разработка даже для других программистов а команде уже не будет представлять сложности. Опять же совершенно божественный автокомплит и рефакторинг в пару действий сильно отрезвляет и даёт понимание, что читстый ES в 2019 допустим только для прототипов, но не как не для серьезных приложений.

Используем MobX в связке с TypeScript, пока 3 месяца — полёт нормальный, удобно. Насколько я понимаю mobx-state-tree тоже проблему типизации решает. Сравнивали с TypeScript или Flow? Есть ли у mobx-state-tree какие-то ещё плюсы?
Скорее не типизации, а предсказуемости что в дереве, какая модель, какие методы, какие типы итп.

Когда пару лет назад я выбирал, что использовать для типизации, TypeScript попросту оказался мощнее. Опять же без нормальной поддержки в IDE ништяки типизации не так понятны, а поддержка Flow на тот момент была ужасно тормознутая. Как мне кажется, Flow — это полумера. Боитесь чрезмерной строгости — noImplicitAny и прочие параметры компиляции помогают на первых порах. И, опять же, разработчиков на TS, как мне кажется, больше, чем тех кто умеет правильно писать на Flow. От части это связано с тем, что на NG 2 стали использовать TS.
По поводу преимуществ, которые дает mobx-state-tree: ненавижу скринкасты, но в свое время я посмотрел один тьюториал, и понял, какой же фигней я раньше страдал.

У mobx-state-tree есть одна киллер-фича — работа с реляционными данными. На уровне снапшотов данных это классический нормализованный стейт, как в Redux. Что позволяет даже использовать Redux Dev Tools. А на уровне бизнес-логики это полноценный объектный граф, как в MobX.

Посмотрел на mobx-state-tree. Сразу не понял в чем профит. Выглядит существенно сложнее чем простой mobx. Если можно расскажите что это Вам даёт?
Сериализация для SSR, строгая структура данных, подписка на изменения в дереве в виде JSON-патчей, кэширование view функций, getter'ов. В общем если углубляться там дофига чего нужного есть.

Ну, к слову, JSON-патчи с помощью immer можно получить и в Redux, и в любом другом immutable state manager .


Хотя MobX мне тоже нравится больше. Да и immer написал автор MobX =)

Есть один не приятный момент, из-за которого может падать приложение, хотя могло бы и не упасть и продолжить нормально работать, например ты указал что ждёшь от АПИ объект в котором есть свойство price, и ты ожидаешь number, а от АПИ пришла string и все, кирдык. Хотя по сути без разницы для вывода цены будет ли это string или number. Даже если вам надо сложить 2 + "3.5" у вас все равно получится 5.5 как ожидается, так что и для расчетов это тоже не особо актуально в JS, другое дело когда вам придет не "3.5", а скажем "london", тогда все печально, но от такого вас и mobx-state-tree не спасет, он просто упадет. По мне для типизации в помощь при разработке ловчее использовать flow или typescript(если прям реальная потребность в нем есть на проекте). Но для меня для типизации данных от АПИ нету нужды, элементарно просто открыть network и все там видно, в каком формате что и как приходит, тем более как обычно в АПИ в ходе разработки может все меняется и ни раз, и вместо того что исправлять в одном месте, ты всегда исправляешь минимум в двух

По моему опыту строгость в апи клиенте Рина сервере идёт только на пользу. У меня был один случай с мобильным приложением. Я бжкжнд писал на codeigniter+mysql. Тк этот стек достался от сайта с которым мобильное приложение было интегрировано. Та в общем то есть что то типа active record в codeigniter и я довольно разработал уже 90%кода когда вдруг обнаружил что драйвер mysql в php числа отдает как строки и я это все так строками и отправляю на мобильное приложение. Короче переделывать было уже поздно так там это и осталось. А если бы было строго то все начало бы валиться сразу и ошибка была бы обнаружена.
Я использую superstruct для валидации любых входных данных от апи, как своих так и с 3-й стороны, и вам советую.
Даже если вам надо сложить 2 + «3.5» у вас все равно получится 5.5 как ожидается

Мы сейчас точно точно говорим о JavaScript? При сложении числа со строкой произойдет конкатенация:


2 + "3.5" === "23.5"

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

Согласен с автором по поводу Redux и его библиотек.
Кто-нибудь использовал Mobx в крупном SPA-приложении?
Какие у Mobx есть минусы в рамках производительности и сложности развития/поддержки приложения?

Я и более чем на 4х проектах, никаких минусов, сплошные плюсы, реально без сарказма

Использовал на 3х проектах. Собственно, на последнем производительность была главным мотиватором переезда с Redux на MobX. Причина почему MobX при всей магии оказывается ещё и быстрее в том, что посто вычислить a + b и оповестить об этом всех подписчиков оказывается быстрее, чем создать объект-экшн, прогнать его про всем редюсерам, сравнить строки, иммутабельно создать новый стейт и если нужно — повторить эту операцию для составных экшнов. Можно каждый из этих шагов оптимизировать, но это будет уже собственное решение. В классическом Redux каждый экшн обрабатывается каждым редюсером (получаем квадратичный рост сложности вычислений), используется сравнительно дорогая операция сравнения строк, вместо дешёвого обращения к методу/свойству объекта, копирование всех свойств в иммутабельную копию так же дороже, чем работа просто с объектом по ссылке, нативные типы вроде Map, Set, Typed Arrays, даже Promise не поддерживаются, в результате их функционал приходится разворачивать в Plan Object, что тоже ест производительность. Вообще, за реализацию и промотирование такой технологии как Redux я бы выгонял из профессии, без права кодить 50 лет. В MobX ничего особенного, он просто написан с применением здравого смысла и заботой о пользователе. Просто работает как нужно, что про него даже статьи не особо пишут (в основном только сравнения), потому что он не является болью.

Redux — это хорошая идея с ужасной реализацией. Хорошая идея — это TEA, которая с успехом используется всеми любителями Хаскеля. И даёт все любимые хаскелистами плюхи — чистоту, простоту композиции и рефакторинг в функциональных системах с развитой системой типов.
Ужасная реализация — это Redux. Который предлагает писать на языке не функциональном, без развитой системы типов, с костыльной дорогой иммутабельностью и кривой функциональной композицией.


Хочешь бойлерплейта и математического доказательства точности своей программы — пиши на Elm.


Во всех остальных случаях — бери MobX и не пытайся засунуть квадрат в круглую дырку.

Хорошая идея — это TEA, которая с успехом используется всеми любителями Хаскеля. И даёт все любимые хаскелистами плюхи — чистоту, простоту композиции и рефакторинг в функциональных системах с развитой системой типов

Чтобы оно давало плюхи, надо dispatch заменить на иммутабельный. А на данный момент это обычная императивщина, без каких-либо плюх. Экшены не композятся, сайд-эффекты вокруг летают и все вот это вот.

Чем больше смотрю на код React и Angular, тем больше жду Blazor в продакшене.
Вам просто не надо использовать никакую оптимизацию в этом случае
можно забыть о Pure Components, shouldComponentUpdate и что вы там ещё используете в этих случаях
вы забудете о проблемах с оптимизацией навсегда.

Может я чего недопонимаю?! Дано: большое сложное приложение, на верхнем уровне меняется @observable значение и компонент верхнего уровня render-ится чтобы отразить эти изменения. Всё древо снизу не мемоизировано (нет PureComputed, Memo, shouldComponentUpdate и пр.). React в рамках 1-ой фазы render-а вынужденно пройдёт по всему древу вглубь до самого последнего листа. Будут вызваны все render методы вашего приложения, будет сформировано с нуля всё vDom-древо вашего приложения, и потом оно же реконслировано (без особого на то смысла).


Я где-то что-то напутал? Или ^ просто не считается проблемой в мире MobX?

Любой observer сам по себе мемоизирован. Другое дело, что это достигается либо через memo, либо через PureComponent, либо через shouldComponentUpdate — так что именно с этой стороны ничего нового mobx не предлагает; проставить всем компонентам декоратор observer ничуть не проще чем проставить им всем базовый класс PureComponent.

Ага, ясно, спасибо. Получается мемоизация на месте, никакой магии тут нет. Впрочем даже в этом случае мемоизация для dump-компонент без mobx не помешает, и react.Memo будет в тему (автору на заметку).

codesandbox.io/s/summer-shadow-z703e
Вот смотрите, наглядный пример, перерендер только того, что надо, а не всего дерева если на верхнем уровне изменился стейт.

Касательно сравнения redux + anything экосистемы с чем-либо ещё, то мне кажется сравнивать тут нужно сопоставимые решения. В случае mobX нужно было показать пример не с counter-ом, а хотя бы какой-нибудь todo-list. А лучше чего-нибудь крупнее, где будет достаточно витееватое древо данных. И показать не сам mobX а mobx-state-tree или схожие решения. А то вы сравнили тёплое с красным.


Redux это one-way driven data flow со всеми вытекающими минусами и плюсами. Когда вы берёте себе на вооружение решение такого рода вы обрекаете себя на лишний boilerplate но взамен получаете цельное приложение мимикрирующее под [чистая функция от данных] + [изолированные от view слоя данные] + [изолированная от всего этого business logic]. Это нужно только в крупных приложениях, разработка которых затягивается на многие годы. Совершенно незачем это тащить в приложения малого и среднего размера (в петлю полезете).


Поэтому все эти сравнения: я в 3 строки написал, то что в redux требуют 10-ка файлов и 200 строк в некоторые степени бессмысленны. Гораздо интереснее и полезнее был бы обзор elm vs react + mobx-state-tree vs vue + vuex vs react + redux + reselect + saga vs angular + rxjs. Все эти связки завязаны на приложения одного масштаба. Хотя сложно отрицать, что их можно написать и проще (но это тема для отдельного холивара).

Ну, mobx — это тоже one-way driven data flow, если геттеры и сеттеры рассматривать отдельно друг от друга. Только вот почему-то бойлерплейта атм меньше.

Я, впервую очередь, имею ввиду "one-driven" в рамках целого приложения. Когда есть отдельная "река данных", как отдельный и цельный, а не разрозненный, слой. И в то же время оторванный от view части. Когда react составляющая это по сути 1 большая pureFunction от данных. Звучит несколько утопично, на деле всё не так радужно, конечно. Но идея именно такова.


И да такое можно построить и с помощью mobx. Вероятно mobx-state-tree этим как раз и занимается (в эту сторону совсем не копал). И да наверное бойлерплейта там меньше. Вот такие сравнения были бы показательны.


А текущая статья это как 99% статей про Svelte где вам показывают 2+2 и спрашивают, а чего это вы все ещё не пишете на Svelte? :)

А текущая статья это как 99% статей про Svelte где вам показывают 2+2 и спрашивают, а чего это вы все ещё не пишете на Svelte? :)

Вы предлагаете мне сделать аналог фейсбука, чтобы вы смогли оценить преимущества MobX?

Для начала вам нужно решить, относительно чего вы хотите показать преимущества MobX. Сравнивать голый MobX и redux довольно бессмысленно. Почему — я выше подробно расписал. Голый MobX вы можете сравнить, скажем, с голым Svelte, с react hooks, с knockout и пр.

Почему — я выше подробно расписал. Голый MobX вы можете сравнить, скажем, с голым Svelte, с react hooks, с knockout и пр.

Я сравниваю преимущества MobX по сравнению с flux — архитектурой (на примере Redux) в приложениях на React. Оба они по сути конкуренты — и то и другое представляется как возможность организовать хранение данных на фронте. Статья так и называется — почему вы должны использовать MobX вместо Redux. Почему я должен сравнивать MobX с Svetle, Knockout и react hooks я, честно говоря не очень понимаю.
Оба они по сути конкуренты — и то и другое представляется как возможность организовать хранение данных на фронте

Они не конкуренты. Вот конкурент. Не сравнивайте бульдозер и девятку. Так можно и с jQuery сравнить.


Почему я должен

Вы никому ничего не должны, что вы.

Похоже что вы не пробовали MobX в бою, поэтому не имеете о нем полного представления

Ну по идее статья написана именно для таких людей? Те, кто пробовал и redux, и mobx, обычно имеют собственное мнение.

Похоже, статья написана для людей, которые используют redux, но не в восторге от его подходв.

Я даже не поленюсь перевезти.

Люди часто часто используют MobX как альтернативу Redux. Пожалуйста учитывайте, что MobX — это библиотека для решения вполне определенной технической проблемы. Это не архитектура, сам по себе MobX — это даже не контейнер состояния. Вышеуказанные примеры надуманны, и я рекомендую использовать вам, такие подходы как инкапсуляция логики в методах и организация ее в контроллерах и сторах. Или как кто-то сказал на HackerNews

«MobX, уже где-то упоминалось, но не могу не присоединиться. Использование MobX означает, что в архитектуре приложения Вам придется самостоятельно побеспокоится, об использовании таких шаблонов управления потоком данных, как контроллеры/диспетчеры/экшены/супервизоры вместо чего-то предлагаемого по-умолчанию для приложений сложнее списка дел.»

Это отсюда если что.

Ясно, вы один из тех кому что-то сказали и он слепо в это верит, а если ещё и статью кто-то написал так это вообще истина в последней инстанции. Учитесь думать и анализировать своей головой пожалуйста, а не чужими, и опираясь на слова по вашему мнению "авторитета". А ещё было бы круто самому все это попробовать не на уровне hello world и todo и потом уже делать выводы и умозаключения, вместо того чтобы ссылаться на одну из миллионы бесполезных статей и чьих то высказываний которые вводят в заблуждение доверчивых людей, выдавая это за истину и что именно так оно и есть как там написано.

Ясно, вы один из тех кому что-то сказали и он слепо в это верит, а если ещё и статью кто-то написал так это вообще истина в последней инстанции.

Я привел, не статью я рекомендацию автора библиотеки, которую он привел в официальной документации.
А ещё было бы круто самому все это попробовать не на уровне hello world и todo

А вы попробовали? Поделитесь пожалуйста.
ссылаться на одну из миллионы бесполезных статей

Полезна ли MobX? Полезна ли документация к ней? Может ли у полезного проекта быть бесполезная документация? А полезная документация у бесполезного проекта?

Я с лично с 2017 года реализовал больше 4х проектов с mobx и все шикарно и без проблем, плюс с этого же периода в нашей компании все начали вместо redux использовать mobx и радости их нету придела по сей день, т.е в рамках нашей компании этих проектов уже больше 10 сложного и среднего уровня и вообще без нареканий, в некоторых пробовали и mobx-state-tree, но в итоге кроме оверхеда и подводных камней — реальной пользы не принес, то чего от него ждут лучше добиться другими путями если уж так надо. Поэтому опыта а меня дофига в реакте с 2015 года как с redux + его зоопарк, так и с mobx с начала 2017 года.

проектов уже больше 10 сложного и среднего уровня

Больше 10 сложных проектов. За 2г. Ух-ты. Что за компания? Несколько сотен разработчиков? :)

200+ сотрудников, из них не все конечно разработчики, но большинство

Кажется Торвальдс писал, что плохие программисты думают об инструментах, а хорошие — о структуре данных. В последнее время в среде тимлидов мелькает мысль, что можно написать инструменты, которые будут ограничивать программиста и заставлять его писать хороший код. Redux тоже из этой парадигмы. Считаю такой подход не эффективным и даже вредным. Программирование это проежде всего про «думать», а не «писать». Никакой инструмент за вас архитектурные вопросы не решит, это ваша работа, как разработчика. Качество кода имеет смысл повышать образованием, а не органичениями. Иначе люди будут всё равно писать говно, только при этом путаясь в лабиринте навязанных правил, пытаясь их обойти, и обходя там, где не стоило, разрушая весь их смысл. В redux уже так происходит с thunk и saga. Почитайте комментарии Дэна про эти библиотеки, он везде пишет, что они слишком мощные и разрушают идею redux. Но проблема не в них, а в том, что redux, пошёл по пути запретов, а не возможностей. Положился на технологию, а не на людей, в результате люди страдают.
Из опыта (а я допытывался примерно у 30 человек, когда работал тимлидом в Яндексе, и примерно столько-же после), больше всего топят за redux люди, для которых это первая подобная технология. Они просто не создавали SPA раньше, не пробывали альтернатив, поэтому верят промо-статьям. К слову сказать, Facebook тоже раньше не создавали SPA, Цукерберг писал на PHP и все знают его авторитарность. Не удивительно, что и React получился похожим на PHP, JSX — просто калька с того, как написаны php-сайты 2000х, js вперемешку с html. Они бы хотя бы посмотрели, как SPA делались в отрасли, но гордыня и самомнение, поэтому сейчас React проходит те же ошибки, которые отрасль прошла 20 лет назад. PR у Facebook хороший, поэтому все новички с ними. Redux тоже вдохновлён серверными языками, но уже другими, функциональными. Хороший сервер должен быть stateless, чтобы можно было распараллелить его на несколько машинок. В серверной разработке часто используют иммутабельные данные, так как это позволяет удобно работать с многопоточностью. На клиенте всё наоборот, но ребята этим никогда не интересовались, поэтому создали говно. Их можно понять, все ошибаются. Нельзя понять тех, кто продолжает топить за это говно, не будучи знаком с другими подходами. Иммутабельность на клиенте — зло. Глобальный стейт мало чем отличается от глобальных переменных, которые зло. Тайм машина классная штука, но нафиг не нужна, если вы не создаёте Text Editor или т.п… Это просто ворует ваше время. Разделять данные и методы — это идея из 70-80, от неё давно ушли, это надуманное бесполезное ограничение.
Да, MobX решает одну маленькую просую задачу — обновлять UI когда обновились данные. Эта задача есть в любом приложении и вообще должна бы идти из коробки в React (во Vue и даже Angular это из коробки). Но в Facebook писали на PHP, а не JavaScript, поэтому посчитали это не важным. Redux был создан для демонстрации тайм-машины для конференции, он для этого хорошо подходит, но он не подходит для большинства веб проектов и не решит за вас проблемы с данными, это ваша работа. Не используйте нерелевантные решения не не промотируйте, пожалуйста, а то одинаковые разговоры каждый раз с колегами происходят и никто дальше двух-трёх «Зачем?» root-cause анализ не проходит.
Тайм машина классная штука, но нафиг не нужна, если вы не создаёте Text Editor или т.п

Даже для современного Text Editor с коллаборативным редактированием тайм машина оказывается бесполезна.

Приятно видеть человека у которого своя голова на плечах и он ей думает.

Почитайте комментарии Дэна про эти библиотеки, он везде пишет, что они слишком мощные и разрушают идею redux.


Я бы еще продолжил. Suspense, к созданию которого как я понимаю причастен Д. Абрамов возможно будет плохо или никак сочетаться с redux. Это пока у меня первое такое впечатление. Возможно кто-то сочинит еще аналог thunk тол ько теперь для работы с Suspense
JSX — просто калька с того, как написаны php-сайты 2000х

Я бы не был так категоричен, по моему мнению это лучший на сегодня шаблонизатор, максимально приближенный к html. В отличие от Vue и Angular, где в кастомных атрибутах в виде строк описывается логика работы — подход, с которым сообщество борется десятки лет, но он почему-то жив и приобретает новые формы.


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

На моей практике стандарты кодирования и стандарты качества (неинструментальные, но все же ограничения) способствуют формированию общности в среде разработчиков, если создаются демократическими методами. ESLint туда же. Не в одном проекте видел достаточно образованных разработчиков, которые в одной кодовой базе использовали кардинально разные подходы — с типизацией/без, функциональный стиль/ООП, thunk/saga, даже различные методы именования переменных. Нельзя сказать, что код каждого был плохой — но при уходе разработчика другим приходилось переписывать то, что он делал, так как не понимали его код.


В остальном согласен, разве что только не MobX обновляет UI, а все же React в данном случае, просто эффективней (хотя крайне мало проектов, в которых важно — обновилось 10 компонентов или 1)

Я бы не был так категоричен, по моему мнению это лучший на сегодня шаблонизатор, максимально приближенный к html.

Так "максимально приближено к html и разбавлено вставками хост-языка" — это и есть пхп-поход. В фейсбуке только заменили строки на хмл-подобную нотацию. И, с-но, с этим реакт-подобным подходом как раз сообщество и боролось — в результате чего появились полноценные шаблонизаторы. Именно так, а не наоборот. Ну, на бекенде.
На фронте же развитие сразу началось с современных технологий (с шаблонизаторами) — но в фейсбуке решили портировать свой устаревший пхп-движок (xhp) на js, видимо, для того, чтобы удобнее было переносить протухшее легаси, и… эта песня хороша, начинай сначала — технологический уровень фронта отбросило на десяток+ лет назад.

Нет, ПХП-подход — это писать в файле страницы всю логику, начиная от запросов в базу.


JSX — это обычный шаблонизатор для HTML, который имеет хорошую и максимально приближенную к Javascript модель для программирования шаблонов.


И да, внутри JSX нельзя ходить в базу и делать запросы на сервер, как нельзя их делать, например, в шаблонах Ангуляра (хотя можно делать в компонентах).

Нет, ПХП-подход — это писать в файле страницы всю логику, начиная от запросов в базу.

Нет, ПХП-подход (ну, тот самый канонический говно-ПХП подход; понятное дело что и на пхп многие пишут по-человечески) — это вермешель из кучи маленьких ф-й, каждая из который выплевывает небольшие куски хтмля.

И да, внутри JSX нельзя ходить в базу и делать запросы на сервер

Точно нельзя? Что-то вроде '<input value={fetch(), this.props.value}} />` не прокатит?

UFO just landed and posted this here

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


Цукерберг писал на PHP и все знают его авторитарность. Не удивительно, что и React получился похожим на PHP

React появился когда Facebook уже был многомиллионной компанией. Вы всерьез верите что CEO Facebook лично принимал участие в дизайне React? А БЭМ в Яндексе под личным контролем Воложа и Сегаловича придумали?


JSX — просто калька с того, как написаны php-сайты 2000х, js вперемешку с html.

Если это единственное что вы увидели в React, то вы не поняли этот фреймворк совсем.


Они бы хотя бы посмотрели, как SPA делались в отрасли, но гордыня и самомнение, поэтому сейчас React проходит те же ошибки, которые отрасль прошла 20 лет назад

Почему вы считаете что не смотрели? В отрасли были Angular и Backbone. React перенял их опыт. Были даже гибридные Backbone+React решения.


Какие ошибки вы можете назвать?


Глобальный стейт мало чем отличается от глобальных переменных, которые зло.

Глобальный стейт больше похож на шину данных, чем на просто переменные. Общая шина данных тоже зло?


никто дальше двух-трёх «Зачем?» root-cause анализ не проходит.

Вот это хороший совет, но вы сами ему не следуете. Более глубокий анализ помог бы вам избежать этих заблуждений, которые вы демонстрируете выше.

React появился когда Facebook уже был многомиллионной компанией. Вы всерьез верите что CEO Facebook лично принимал участие в дизайне React?

React — это порт XHP, а XHP появился задолго до реакта. А архитектура кода, под который писался XHP — конечно же, задолго до самого XHP. Так что преемственность тут прослеживается весьма четко.


Если это единственное что вы увидели в React, то вы не поняли этот фреймворк совсем.

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

А БЭМ в Яндексе под личным контролем Воложа и Сегаловича придумали?

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

Если это единственное что вы увидели в React, то вы не поняли этот фреймворк совсем.

Просветите? setState который по хорошему пора заменить геттерами-сеттерами? Или хуки, которые являются органиченной версией наблюдателя? Или контекст, который разрушает инкапсуляцию? Что вас привлекает в React? Несмотрю на стёб я считаю JSX хорошим решением. Мой тезис в том, что так как ребята не специализировались на фронтенде — они допустили множество других ошибок, которые портят это решение.

Какие ошибки вы можете назвать?

Прежде всего ошибку приоритезации: они решали проблему, которой не было, и не решили актуальные. В частности обновление view при обновлении model (из за чего мы и имеем пляски с Redux, MobX, т.п.). Для сравнения, Vue не только решили эту проблему из коробки, а так же добавили биндинги вроде onkeypress.enter или onclick.preventdefault, что мелочи, но сразу показывает, что они в теме, так как это очень частые операции. Сравните с навешиванием событий в React, когда до стрелочных функций приходилось писать bind в конструкторе на каждый обработчик. Это очень частая операция, очевидно нужно сделать её краткой и удобной, но ребята этого не понимают. Я не думаю, что они глупые, я думаю у них просто не тот бекграунд. Они никогда не создавали SPA на нативном JS и не знакомы с культурой, стандартами и лучшими практиками, поэтому навязывают сообществу свои, «революционные», на самом деле непродуманные.

Глобальный стейт больше похож на шину данных, чем на просто переменные. Общая шина данных тоже зло?

Общая шина нарушает фрактальную природу программы. Можно использовать её в какой-то части программы, можно во всей программе, если программа маленькая, но общая шина не масштабируется. Представьте, если бы весь интернет, все уровни OSI были написаны с использованием одной шины, без разделения на слои? В своё время я запрещал своим сотредникам, начитавшимся хайпа, использовать глобальную шину во фронтенде, и ни разу не пожалел об этом, потому что модульность и возможность инкапсуляции гораздо более ценны, а глобальная шина ничем не лучше локальных наблюдателей. Даже если нужно сделать сквозной функционал, вроде логирования всех событий, я бы продпочёл это делать локальными шинами, наследуемыми от общего родителя (простой вариант), или через Стратегию с передачей логгера композицией, или через dependency injection, что не особо популярно в js-среде, но очень мощно, гибко и не обладает таким количеством недостатков, как глобальная шина. Случай из моей практики когда шина действительно хорошо сработала — геймдев, бекенд, около 50 сущностей, каждая из которых влияет на множество других (действия, перки, скилы, предметы, другие игроки и их аттрибуты). Когда у вас есть кластер из сильно связанных сущностей (и связанных не из-за плохой архитекруры, а по бизнес-требованиям), решением будет или шина или медиатор, шина часто оказывается проще. Но таких приложений мало. И даже в этом случае шину имеет смысл делать не истинно глобальной, а общей на сильносвязанный кластер классов.

Вот это хороший совет, но вы сами ему не следуете

Не говорите гоп пока не докажете ошибочность моих суждений (за что буду благодарен). Пока ничего не доказали
Я не думаю, что они глупые, я думаю у них просто не тот бекграунд.

Не, дело не в глупости и не в бекгруанде, а в квадратно-гнездовом способе мышления.
Достаточно issues на гитхабе почитать — там постоянно возникает ситуация когда фичу Х не делают удобной для 99% кейзов т.к. она станет неудобна в 1% оставшихся кейзов. Вместо этого ее делают удобной для 1% кейзов и, с-но, неудобной для остальных 99%. Понятно, что тут нет логики — но это стандартный способ рассуждения разработчиков реакта. Именно оттуда и упомянутые вами костыли с пробрасыванием ф-й в качестве пропсов — это вполне осознанные решения все.

Неудобно для кого-то !== Неудобно для всех.
Когда кто-то думает что это не удобно для всех по любому, скорее всего это не так) Все люди разные и для всех удобства разные.
Неудобно для кого-то !== Неудобно для всех.
Все люди разные и для всех удобства разные.

В данном случае речь не для кого, а в каком случае, и под удобством подразумевается полностью объективная (т.е. для всех одинаковая) вещь. Когда вам не надо совершать специальных действий для использования фичи — это удобно. А когда надо — это неудобно.
Вот хорошая архитектура — это такая, в которой удобно для стандартных кейзов и неудобно (опционально) для нестандартных. А в квадратно-гнездовой архитектуре — все наоборот. Реакт — типичный пример квадратно-гнездовой архитектуры.


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

Когда вам не надо совершать специальных действий для использования фичи — это удобно. А когда надо — это неудобно.

Питонисты, да и вообще фанаты строго типизированных языков с вами не согласятся. :) "Явное лучше неявного" и так далее. Надо чётко объявлять типы в коде, явно кним приводить, а не доверять автоматическому приведению в PHP или JS. А для сторонников слабой типизации и "магии" языков и фреймворков удобно написать пару строк для решения задачи, а не мучаться с соответствием типов.

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

Я наблюдаю, что в программах на строго типизированных языках есть нефункциональный код связанный с типобезопасностью. Декларации самих типов, объявления типов переменных, параметров, результатов и т. п., формальные (без изменения данных) приведения типов и т. п. Вы считаете, что этот код никак не связан с тем, что языки строго типизированы? Или считаете, что для появления этого кода не совершаются никем никакие дополнительные действия?

Как правило, у этого кода есть некоторая цель, отличная от просто использования фичи. А если такой цели нет — от него точно так же стремятся избавиться.

Очень часто именно для использования. Вернее глобальная цель есть у введения типобезопасности на проекте, но для рядовых разработчиков, которые типы пишут часто этот код именно для использования чего-то, чтобы просто скомпилировалось для начала, а потом ещё и код ревью прошло у того, кто за типобезопасность переживает.

Питонисты, да и вообще фанаты строго типизированных языков с вами не согласятся. :) "Явное лучше неявного" и так далее.

Здесь явное/неявное не при чем. Я говорю о том, что вам либо приходится совершать некие специальные действия, чтобы все заработало (причем смысла в этих действиях никакого нет, в отличии например от типов или какого-то явного описания, вы просто боретесь с фреймворком), либо не приходится. Речь просто о том, что стандартный сценарий использования сломан.


Ну вот например если вам надо повернуть ключ зажигания, чтобы машина завелась (при этом она не заводится от того что вы в машину сели или просто к ней подошли) — это "явное". А когда вам надо для того, чтобы ее завести, приоткрыть багажник под определенным углом и хлопнуть дверцей (одновременно с тем, чтобы ключем покрутить) — вот это сломанный сценарий :)

UFO just landed and posted this here

Очень типобезопасность напоминает :) Когда разработчики библиотеки решают, что я случайно могу передать значение не того типа, которого они ожидают. При этом если мне вообще ничего не надо передавать, приходится делать какой-то null object или передавать undefined

Очень типобезопасность напоминает :)

Никак не напоминает. В случае с типами вам не надо ничего делать — если ф-я ожидает, например, число, и вы суете туда число, то это just works.


А кнопку нажать, чтобы завелась не удобнее будет? А ещё лучше вообще без активных действий на заведение направленных. Сел на водительское сидение, например, и она завелась.

А это уже как раз из разряда неявного поведения. Дело вкуса. Мы обсуждаем другой кейс.


Вот еще пример (как раз близкий к "случайно передать не то") — это история с getDerivedStateFromProps и prevProps. Иметь возможность обратиться к prevProps — это вполне стандартный сценарий использования данной ф-и, он нужен очень часто. Но его не добавили. Не добавили почему? Потому что при первом рендере prevProps = undefined и это могло бы кого-то смутить. Теперь все вынуждены для данного кейза дублировать пропсы в стейт.


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


Ну это уже не говоря о том что вообще сделали getDerivedStateFromProps статик методом из-за чего ее стало крайне неудобно использовать в принципе. А статик методом она сделана почему? Чтобы нельзя было из метода обратиться к текущим пропсам, все верно :)


Причем был работающий componentWillReceiveProps, который делал свою работу и всех устраивал. Кроме пары людей с квадратно-гнездовым способом мышления :)


К слову, в других фреймворках (всякие ангуляры, вуе, исчезающие свелте и даже прочие, не к ночи будут понмянуты, $mol) таких проблем просто не стоит.

ЕМНИП, они убрали componentWillReceiveProps ради concurrentMode. Просто потому что при асинхронном рендеринге может быть выкинуто несколько "фреймов" компонента и prevProps "отстанут" на несколько циклов рендеринга. Т.е. результат componentWillReceiveProps будет зависеть от того насколько тормозит рендеринг. А это уже дичь какая-то.


Еще в угоду concurentMode они пожертвовали корректностью работы библиотек, основанных на синхронных подписках. Как Redux и MobX ага =)


Хотя в целом, я с вами согласен. Завтраками с concurrentMode они кормят нас уже два года. Со стороны ситуация выглядит абсурдно — фичи еще нет, а люди от нее уже страдают =) И вообще не очевидно, будет ли от этого реальный профит. А не только красивые презентации на конференциях.

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

А, квантование вычислений. Насколько я понимаю, вычисление рендер-функции всё же синхронное, так что MobX ломаться не должен.

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

И не вызывается никакого колбэка когда компонент выкидывается?

Вызывался бы — не было бы проблемы.

Просто потому что при асинхронном рендеринге может быть выкинуто несколько "фреймов" компонента и prevProps "отстанут" на несколько циклов рендеринга.

Так с getDerivedStateFromProps проблема остается — если компонент должен отслеживать изменения пропсов, а они пропускаются.
Нормальным решением была бы возможность для компонента указать, должен ли он работать в concurentMode (по дефолту — нет, по крайней мере первое время для сохранения обратной совместимости) либо не должен (если нам нужен четкий контроль). Но у фейсбука как обычно, да :)

А кнопку нажать, чтобы завелась не удобнее будет? А ещё лучше вообще без активных действий на заведение направленных. Сел на водительское сидение, например, и она завелась.

Сейчас Парахин, например, очень активно в технологической стандартизации участвует и лично вникает в детали

Он уже покинул компанию. Так что его методы работы явно не история успеха.


Что вас привлекает в React?

В первую очередь легкость создания переиспользуемых компонентов. В Angular и Vue на каждый компонент обязательно нужно создавать видимый html-тэг, а в React компоненты будут видны только в React Devtools, оставляя пользователю чистый компактный html.


В частности обновление view при обновлении model (из за чего мы и имеем пляски с Redux, MobX, т.п.).

Не могу на эту тему дискутировать, в моих проектах достаточно нативного setState, работает без проблем, велосипед изобретать не приходится (Мы пишем UI-библиотеку).


Vue а так же добавили биндинги вроде onkeypress.enter или onclick.preventdefault, что мелочи, но сразу показывает, что они в теме, так как это очень частые операции

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


Сравните с навешиванием событий в React, когда до стрелочных функций приходилось писать bind в конструкторе на каждый обработчик

В до-es6 времена был React.createClass, в котором все методы автоматически байндились к this, это в версии на es6-классах эту функциональность убрали, потому что появился удобный нативный синтаксис.


Общая шина нарушает фрактальную природу программы. Можно использовать её в какой-то части программы, можно во всей программе, если программа маленькая, но общая шина не масштабируется.

Понятно. Так и Redux эту природу не нарушает. Мы же не используем connect в каждом компоненте, а только там где нам нужны глобальные данные. Что именно должно отправиться в глобальный стор, а что остается локально – отдельный хороший архитектурный вопрос.


Как по мне, грамотный тимлид подскажет как правильно приготовить Redux, а не будет авторитарно его запрещать. Кстати, а если не Redux, то что использовать взамен?

Как по мне, грамотный тимлид подскажет как правильно приготовить Redux, а не будет авторитарно его запрещать. Кстати, а если не Redux, то что использовать взамен?

1) Тимлид увы не всегда грамотный и/или они грамотный но, для людей который схожи по мышлению с ним.
2) Если не Redux, то MobX. А ещё лучше, если не MobX то что? Ничего, лучше ничего не придумано.
Хватит уже эту дичь втирать и толкать)))

Это не дичь, а более продвинутая система реактивности, чем в MobX.

Чем она более продвинута-то? Тем, что одна и та же функция используется как для чтения значения, так и для записи?..

Двусторонний поток данных.
Абстракция от асинхронности.
Контроль времени жизни значения.
Человекопонятные идентификаторы.
Во вторых атомах поддерживается квантование вычислений.

Не для всех двусторонний поток данных однозначное "лучше". А с человокопонятностью в $mol ві пошутили? Что $ значит у вас я уже не понимаю :)

Не все способны спроектировать простую надёжную и гибкую архитектуру. И что?


Не пошутил.


Это префикс глобалных неймспейсов.

Ну вот односторонний поток данных и является простым. Двусторонний обмен сложнее концептуально.


Вы не помните критики ваших примеров в топиках типа этого? По памяти, люди, разработчики здесь на Хабре, очень часто советуют вам сделать DX при работе с $mol проще и понятнее. Прежде всего в плане читаемости.


А зачем? На него логика какая-то завязана? И они реально глобальные?

Да нет, двусторонний как раз проще. Забиндил одно свойство на другое и всё работает. Без свистоплясок с экшенами, событиями и прочими стримами.


А какие именно там проблемы с читаемостью? И какое она отношение имеет к генерации идентификаторов из имён заданных прикладным программистом?


Реально глобальные. На них сборка завязана.

Это пока не нужно выяснить, а почему вот это свойство имеет неожиданное значение. С однонаправленным "биндингом" мы всегда знаем откуда оно пришло и легко можем определить где оно изменяется в принуципе (особы хардкор динамический языков в расчёт не беру), с двусторонним где угодно может оказаться биндинг и значение присвоено не искомой перемнной а сбинженной, возможно не на первом уровне, а по длинной цепочке.


А чего не сделать было человекочитаемый префикс типа global_?

Просто mol_mem? А лучше MolMem?

У вас аллергия на баксы или что? Доллар замечательно позволяет отличать локальные имена и глобальные. На всякий случай — имена есть не только у переменных, но и у полей объектов, методов и пр.


PascalCase ни чем не лучше: https://github.com/eigenmethod/mol/issues/312

У меня аллергия на нарушение общепринятых соглашений. С аллегрией на "бакс" я не смог бы писать на PHP скоро как половину жизни.


Так уж сложилось, что общепринятым в экосистеме JS является именнование конструкторов/класов в PascalCase, а переменных, параметров и членов клаасов в camelCase.


И в целом не понимаю необходимости отличать локальные имена от глобальных. Наличие имён $name и name в одном скоупе в любом случае допускает их смешение хоть при написании кода, хоть при чтении. А если такого наличия нет, то префикс избыточен.

Понятно зачем — чтоб облегчить "понимание" кода программой. Оказывается, это расширенный стиль, которы я называю Zend_Loader_Autoloader style.


То есть это не человекочитаемый код, а машиночитаемый. Не для человеков ведь этот префикс, а для машин.

Он для одинакового понимания как человеком, так и машиной в любом контексте.

Вот сделали бы сравнение React+MobX vs React+$mob_atom — может народ бы и понял, что второе круче.

Тут сам реакт мешает многим плюшкам.

То есть для реакта лучше не придумано всё-таки? :)

Придумано. Отсутствие реакта.

Ну вот на проекте тысячи react компонентов, никто ресурсов на полное переписывание не выделит. Принято решение использовать внешний стейт-менеджер. $mob_atom не конкурент redux, mobx и, возможно, rxjs?

Переписывать можно и по частям.
При переписывании из 1000 реакт компонент станет 100 мол компонент. Объём кода уменьшится тоже раз в 10.
А главное, уменьшится объём копипасты и сложность поддержки.


Без существенного рефакторинга, замена мобх на мол_атом мало что даст. Затея эта бессмысленная.

Уточню 1000 реакт компонент с вёрсткой, грубо за 10000 строк html. И не замена мобх на мол_атом, а замена ReactComponent.setState на что-нибудь. Какие плюсы будут у мол_атом и какие минусы?

Это ж сколько там копипасты в 10K sloc html.


Да те же, что у мобх + те, что я перечислил выше.

Так может сделаете пример для реакта?

Не сделает, он же не любит реакт)

Ну, может, $mol любит больше и готов для его популяризации связаться среактом

И каким волшебным образом это популяризирует $mol?


Впрочем, влюблённым в Реакт могу предложить посмотреть на этот проект: https://github.com/zerkalica/reactive-di
Автор долго боролся с архитектурой Реакта, чтобы завести в нём фичи из $mol. В итоге плюнул и стал пилить на $mol.

Ну, например, мы сейчас выбираем стейт менеджер для реакт-приложения. Предварительно это будет MobX. Но если сравнение покажет, что использование $mol позволит писать, тестировать, развивать и поддерживать код эффективней, увеличит скорость доставки фич до продакшена, то вполне серьёзно его рассмотрим. Аллергию на доллар решу введением алиасов :)

$mol_atom — лишь маленькая часть $mol. Прикручивать его к Реакту я пробовал, но запарился изобретать костыли, жертвуя фичами. Вам проще reactive-di попробовать. Если же от Реакта вам нужен только JSX, то скоро сделаю пример кастомного jsx обработчика с $mol_atom2.

Понятно. Увы, мне от стейт-менеджера нужно избавление от бесконечных вызовов this.setState в React-компонентах, а не избавиться от всего, кроме JSX. Спасибо за наводку, извините, что раньше на этот коммент не ответил.

А какая альтернатива? Кроме $mol

Заметную часть самой либы (лайфциклы, хуки, контексты плюс общий принцип работы компоннетов) и остальной экосистемы (роутинг, формы, интерналициализация — навскидку из крупного). Перписывать это на принципиально другую платформу нереально.

Плюсую, чтобы написать counter не нужна библиотека для управления состоянием. Актуальнее описать какие проблемы возникают при написании больших React приложении, и как каждая из библиотек пытается их решать.

Redux и MobX решают одну и ту же проблему/задачу, если не разводить демогогию и цепляться к мелочам. Из моего опыта все кто опробовал MobX в бою больше никогда к Redux не возвращался, конечно же будут индивидумы которые попробовав в бою MobX останутся с Redux'ом, но я таких не знаю. Знаю только тех кто топит за Redux начитавшись непонятных статей и не попробовав при этом MobX на своей шкуре.

Даже потратив 5 минут, уже могу сказать, что эти библиотеки решают разные проблемы.

ru.wikipedia.org/wiki/Flux-%D0%B0%D1%80%D1%85%D0%B8%D1%82%D0%B5%D0%BA%D1%82%D1%83%D1%80%D0%B0

github.com/mobxjs/mobx

Unlike many flux frameworks, MobX is unopinionated about how user events should be handled.

This can be done in a Flux like manner.
Or by processing events using RxJS.
Or by simply handling events in the most straightforward way possible, as demonstrated in the above onClick handler.

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

Так если Вы уверяете всех, что имеете богатый опыт, поделитесь им. С какими проблемами Вы сталкивались? У вас не перерисовывался компонент? или что-то посложнее?

Пока вижу только доводы в духе: «Мне и моим одноклассникам всё понравилось».

Так не было ни проблем, ни боли с mobx и нету по сей день

Знаю только тех кто топит за Redux начитавшись непонятных статей и не попробовав при этом MobX на своей шкуре.

А я знаю тех, кто топит за React и Angular, не попробовав $mol на своей шкуре.

UFO just landed and posted this here
Некоторые аргументы очень странные:

Да, всё верно, вместо каких-то функций, которые изменяют объекты, вы можете использовать классический ООП подход, с классами, их свойствами и методами.

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

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

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

Если посмотреть примеры выше, то можно увидеть, что в случае с MobX я не использовал pure component и это не ошибка.

Зато использовал его в случае с редаксом и это ошибка. Редакс считает все компоненты чистыми по умолчанию и не перерендеривает их, если mapStateToProps вернул эквивалентный результат.

Про setState аргумент вообще надуманный и большая часть примеров с ним это просто плохой код. Например, передача в setState объектов, хранение там таймеров, использование хуков без зависимостей и тому подобное.

Не советую, функции должны быть чистыми и простыми без своего состояния, для всего остального есть классы с человеческим жизненным циклом, а в качестве стейт менеджмента сбоку используйте MobX или Redux. Сэкономит время и нервы в будущем вам и тем, кому придется иметь дело с вашим кодом и вашей архитектурой. MobX ещё сэкономит вам прилично времени и полностью развяжет руки, стоит только попробовать и все, обратно уже не захочется)

Основная проблема таких проектов — отсутствие коммъюнити, и как следствие, фрагментация.
Есть куча библиотек, построенных вокруг использования Proxy для отслеживания изменений. Например:


  • easy-peasy — Redux-like стор с Immer внутри и селекторами снаружи
  • react-tracked — а здесь наоборот, Proxy используются для отслеживания зависимостей при рендеринге, как в MobX
  • overmind — а здесь реализованы обе фичи, почти как mobx-state-tree. Но нет JSON-patch и объектного графа.

Vuex надо сравнивать в контексте Vue, а тут React.js. Vue + Vuex дружелюбная штука после реакта, в отличие от ангуляра. Но тут уже дело вкуса, я пробовал боевые задачи делать на vue чтобы реально оценить и сравнить для себя с реактом, пришел к выводу что react + mobx серебряная пуля для задач и проектов любой сложности, просто нужен большой опыт и умение все это готовить, я не вижу даже что ещё можно улучшить в реакте или в мобе чтобы было ещё приятнее кодить, все это в купе с es2019 для меня просто эталон. По желанию можно прикручивать typescript, но там тоже есть нюансы.

Для typescript есть ангуляр. Так по вашему опыту, реакт показал себя лучше vue?

Для меня да, реакт более изящный и красивый нежели vue, про angular вообще молчу. Vue и Angluar это именно фреймворки которые диктуют свои правила и загоняют в рамки, а реакт это просто библиотека, но именно в умелых руках превращается в идеальный инструмент для SPA

я не вижу даже что ещё можно улучшить в реакте или в мобе чтобы было ещё приятнее кодить
Например, можно еще:
1. Сделать уже из реакта фреймворк, а не конструктор фреймворков. Хорошо, когда есть возможность писать с нуля и самому выбирать технологии. Но в реальности из-за запросов заказчика и скорого дедлайна зачастую приходиться брать готовые решения, каждое из которых реализовано по разному и на разных технологиях, и которые надо переделывать под проект. И тут тебя еще «радуют» проблемы популярных решений.
В vue удобно сделано. Можно при установке выбрать, что ставить (vuex, router)
2. Композицию сделать на props, а не на компонентах, чтобы уже перестать смешивать логику и view, и не писать очередные замены миксин.
3. Условия, циклы в jsx добавить.
4. Двунаправленный биндинг добавить. Можно конечно стейт передавать в кастомные input и самостоятельно реализовать биндинг. Но это не вариант.

Уверен, еще много чего найдется улучшить.

Для меня это не улучшения, а наоборот, если вам это надо, то вам надо писать на vue или angular или ещё на чем нибудь.


То какой сейчас react, mobx и ecmascript, более чем достаточно для тех, кто умеет это готовить.


По поводу заказчиков, никто вам не мешает найти нормальную работу, где нету этого булшита тяп ляп и на скорую руку лишь бы работало. Тут вы никогда не станете реальным спецом. Я последнее 3 года от такой работы просто отказываюсь и работаю только на проектах с нуля где не надо чтобы все работало похер как, лишь бы побыстрее закодить. И поэтому пишу всегда в свое удовольствие и закладываю наработанную с годами архитектуру и постоянно ее совершенствую и не знаю никаких бед и проблем. Mobx, react, moment, axios и все, больше ничего не юзаю, кроме редких исключениях типо задачи когда надо например визуальный редактор html прикрутить.

Смысл переходить на vue или angular, если там всего этого тоже нет)

Это хорошо, что у вас все так хорошо сложилось. Я в жизни не видел ни одного разраба, ни одной фирмы, где прям все хорошо. Только если на словах. У всех свои проблемы. Ладно, не об этом речь.
Наконец-то то я вернулся домой и теперь за компом, теперь можно нормально писать, а не с телефона)
1,3,4 пункты как минимум присутствуют в Vue
А по второму пункту — смешивание логики и view, ну тут уже все зависит только от того, как код напишите, это не реакт ставит вас в какие-то жесткие рамки
Насчет 1 и 3 пункта, сомневаюсь.
Полноценный фронтенд фреймворк для меня, это когда можно из доступных из коробки фич сделать приложение с клиент-серверным взаимодействием, валидацией, роутингом, стором без явной необходимости скачивать плагины, делающие эти фичи гораздо лучше фреймворка.
Что касается циклов и условий в реализации JSX для Vue, я не знаю, есть ли они там. С Vue я мало знаком.
Ну и к тому же в Vue тоже свои недостатки есть. Не думаю, что один из них (react, vue) настолько лучше другого, что при знании одного, стоит переходить на другой.

По второму пункту.
Реакт изначально делался со смешиванием логики и view. Разделить то можно, до тех пор, пока нескольким компонентам не понадобится общая логика. Решения вроде миксин и HOC по вынесению общей логики вне компонентов не очень себя показали. Решения аналогичного директивам попросту нет. Тут все-таки реакт ставит в жесткие рамки. В общем, реакт гибкий, но мог бы быть еще гибче.
Да, появились хуки. Но неизвестно, куда они заведут. Через пару лет видно будет.
Думаю я не открою америку, но у большинства разработчиков (я не исключение) уже давно написан и подготовлен каркас (который всегда естественно модернизируется чтобы быть лучше и лучше в последующих проектах) в котором уже заложена архитектура и базовые компоненты которые вам так необходимы, такие как роутинг, валидация форм и т.д. и т.п. это не нужно писать каждый раз с нуля, ты как бы собрал свой собственный фреймворк как конструктор и используешь его, используя только плюсы той или иной библиотеки, а если они все или какая-то из них говно, то просто пишешь свое решение и используешь его.
Mobx, react, moment, axios и

… приложение уже весит 125 кб со всеми способами сжатия.


Для сравнения: весь mol.js.org — 85кб даже без минификации.

На дворе 2019 год, 1 раз загрузил(очень и очень быстро, если ты не а африке живешь) и всё, оно у тебя в кэше.
Делай генерацию html на бэке и забудь о JS на клиенте, ну или прям по минимому, так у тебя вообще весь будет, это вес твоей странички

Если это landing page, то есть смысл сделать так, что бы первая загрузка была максимально быстрая, если у тебя это именно приложение, то вообще пофиг, это последнее на что стоит смотреть, тем более если уже так хочется, то можно сделать SSR и пользователь увидеть все при первой загрузки так же быстро и сразу, а интерактивность начнется уже когда (очень очень быстро) загрузится и положится в кэш твой бандл

А не думали выкинуть moment в пользу какого-нибудь модульного date-fns? Это существенно уменьшит вес бандла. Единственное, чем можно объяснить сейчас использование moment — это legacy. Или библиотеки UI компонентов под реакт, где он прописан в зависимостях.

Да я на самом деле обычно вообще с датами работаю самостоятельно написав только функцию для приведения в нужный формат и конвертер в формат который требуется для бэка, в 95% только это и требуется. А moment я указал так чисто, для особо ленивых, как привычную либу)
UFO just landed and posted this here

И зачем мне ваши костыли? У меня нет проблем с размером приложения.

Присоединяюсь к «выводу что react + mobx серебряная пуля для задач и проектов любой сложности» — задачи, стоящие перед frontend-разработкой решаются насколько элегантно, что не приходится раскручивать цепочки вида компонент-родительский компонент-контейнер-селектор-экшен-тип экшена-поиск затрагиваемых сторов-обработчик АПИ запросов-нормализатор, которые присутствуют в более-менее сложных проектах на Redux. Практически ничто не мешает свободному полету мысли и созданию качественного приложения. Разве что Typescript я бы все же не прикручивал — мороки намного больше, чем профита, по моему опыту.
Красавчик, я рад что хоть нас и меньшинство, но все же мы есть (те кто мыслят критически и насрать на любой «авторитет», делай именно так как реально удобно, комфортно и т.п., для этого своя голова на плечах и есть)
P.S я тоже против Typescript на фронте, на бэке ещё можно подумать.
Как раз зашёл чтобы оставить комментарий про vue :)

Если в двух словах, то vue, кажется, умеет из коробки всё, что умеет mobx. Ну например, давайте сделаем счётчик с моделью, по всем канонам mobx:

models/counter.js
export default {
  counterValue: 0
}

components/counter.vue
<template>
  <h1>Counter value: {{model.counterValue}}</h1>
</template>

<script>
export default {
  name: 'Counter',
  props: ['model']
}
</script>

App.vue
<template>
  <div id="app">
    <Counter v-bind:model="counterModel" />
  </div>
</template>

<script>
import counterModel from './models/counter.js';
import Counter from './components/Counter.vue';
export default {
  name: 'app',
  components: {Counter},
  data: () => ({counterModel})
}
</script>


Даже следить за изменением свойств можно: просто добавить в компонент
watch: { 'model.counterValue': function(value){} }
Ах да, я же не написал обновление счётчика?

components/counter.vue
<template>
  <div>
    <h1>Counter value: {{model.counterValue}}</h1>
    <button v-bind:click="incrementValue"> + </button>
  </div>
</template>

<script>
export default {
  name: 'Counter',
  props: ['model'],
  methods: {
    incrementValue: function(){
      this.model.counterValue++;
    }
  }
}
</script>

Логично, Vue это фреймворк, а не библиотека чисто для рендеринга как реакт, который усиливается mobx и становится идеальным))

Эмм. Взять голый редакс и сравнить его с 50 КБ монстром?
Незачем на редаксе писать ТАК, есть куча хелперов.

Одна из проблем мобыкса, в контексте сравнения, это то что внутренняя модель приложения описывается внутри view слоя! С редаксом проще отделить model от view.

P.S. github.com/artalar/flaxom#why-not-mobx

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

MobX позволяет писать более чистый и понятный код.

Только до тех пор пока вы пишете каунтеры. Как только у вас начнутся computed property — вы повеситесь. Одна проперти должна обновлять другую, которая загружает данные от третьей.


Лично по мне, это звучит как полная ерунда.

Отличный аргумент! Можно я тоже буду его использовать?


Да, всё верно, вместо каких-то функций, которые изменяют объекты, вы можете использовать классический ООП подход, с классами, их свойствами и методами. Пусть вас не пугают декораторы(@) внутри, они просто добавляют функционал, необходимый для отслеживания изменения данных.

Вот это демагогия 99лвл. Вместо чистых функций предлагают какие-то декораторы, от которых даже в стандарте отказались.


MobX позволяет писать меньше кода

Есть куча библиотек чтобы писать меньше кода на redux.


Если посмотреть примеры выше, то можно увидеть, что в случае с MobX я не использовал pure component и это не ошибка.

То есть если корневой компонент все таки перерендеривается — мы радостно все перерендиваем?


MobX это ultimate решение == не требующее дополнительных библиотек, лишит вас всех этих прелестей, поэтому с ним бизнес логика вашего приложения будет чистой, как слеза младенца.

С каких пор комбайн делает бизнес логику чище?


Пятая причина — возможность отказаться от setState

А это как связано с redux? Использование redux не подразумевает setState


Я уж молчу что автор засовывает в setState таймеры. Зачем?


Процитирую автора, чтобы выразить свое мнение о статье: "Лично по мне, это звучит как полная ерунда."

Только до тех пор пока вы пишете каунтеры. Как только у вас начнутся computed property — вы повеситесь. Одна проперти должна обновлять другую, которая загружает данные от третьей.

И в чём же тут проблема-то? Это как раз в redux веселиться приходится.


То есть если корневой компонент все таки перерендеривается — мы радостно все перерендиваем?

Процитированная вами фраза "и это не ошибка" как раз и означает, что нет, мы не перерендиваем все. Observer вообще никогда не рендерится без необходимости.

На странице меняем тайтл, реакт берет и всё что в нем перерендеривает. Или я не понял как это mobx позволяет избежать такого.


Пока перечитывал код поймал автора на ещё одном чите — в мобх в observer он подает компонент-функцию, а в redux пишет прям class Page extends PureComponent {render(){}}. Да как так-то.

Не говоря о том, что вместо

Достаточно импортировать директивой import ВСЕ нужные функции в объект и просто передать его в стандартный bindActionCreators(), например, так:
import * as actionCreators from './actionCreators';
const mapDispatchToProps = (dispatch) => ({
    actions: bindActionCreators(actionCreators, dispatch)
});

А остальной копи паст куда девать?)

Копипаст чего именно? Давайте по пунктам.

С фига ли Реакт будет перерендерить компонент, когда вызов shouldComponentUpdate вернул false?

Какую-то дичь вы набросали, вот как правильно писать — codesandbox.io/s/summer-shadow-z703e

идеальная работа mobx перендер только того, что поменялось.

Не, дичь набросали вы, сделав RerenderTest observer'ом.


Со слов автора, одно из преимуществ modx перед redux в том, что можно забыть про React.PureComponent.


А теперь оказывается что забыть можно только потому, что компоненты надо оборачивать в observer. И как бонус выясняется что modx манкейпатчит shouldComponentUpdate у компонента снизу (что за дичь?) и "It is not allowed to use shouldComponentUpdate in observer based components."

Так и где и в чем реальная проблема то?? Все работает как надо, быстро, четко, минимум кода, минимум головной боли. Всего лишь компоненты должны быть observer.

Если мы полностью владеем кодом всех компонентов проекта, то проблем нет. А вот если используем какой-то визуальный фреймворк, то тут уже интереснее. У нас был затык с third-party TreeView, который рекурсивно рендерит сам себя для вложенных элементов дерева. Решений два:


  1. Как-то пропатчить TreeView, чтобы он стал обсервером. С mobx-react 5 для этого достаточно вызвать observer() на TreeView, т.к. по счастливому стечению обстоятельств это был классовый а не функциональный компонент. Но это блин махровый манки-патчинг в самом худшем его проявлении.
  2. Вызвать mobx.toJS() на корневом узле данных дерева, как советовали в одном issue от mobx. Это сразу подпишет рендеринг всего дерева на любое изменение данных. Ну и здравствуй пересоздание развесистых объектных графов на каждый чих. И прощай производительность.

В защиту mobx можно сказать что с Redux, как превратить рекурсивный third-party компонент в connected, тоже непонятно.

Но это блин махровый манки-патчинг в самом худшем его проявлении.

В чем конкретно он доставляет проблемы именно вам или вашему проекту?

Сейчас, ясное дело, ничем. А вот при выходе следующей версии библиотеки, авторы могут: заменить class компонент на хуки; реализовать таки sCU(). И наша карета превратится в тыкву.

Второй вариант не просто так советовали. Если TreeView сделан для отображения иммутабельных данных — значит, ему надо передавать иммутабельные данные.


То, что вам удалось отманкипатчить TreeView — это хорошо, но не гарантировано. И эта негарантированость — проблема не mobx, а TreeView.


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

Да это понятно. По хорошему, TreeView должен иметь какой-нибудь опциональный prop renterTreeNode, в который мы могли бы запихнуть хоть observer(TreeView) хоть connect(...)(TreeView).


Но тогда непонятно, что делать самим авторам UI-фреймворков под реакт:


  • Поставлять отдельные версии компонентов под иммутабельные или реактивные данные? И если под иммутабельные еще можно, поскольку это все еще plain-объекты, то под реактивные — это зависимость от mobx (или чего там еще).
  • Делать все промежуточные компоненты конфигурируемыми (те, что пользователи фреймворка не используют напрямую, вроде TreeNode для TreeView)? Это уже интереснее, но попахивает каким-то внедрением зависимостей.

А вот о стандартном механизме DI должны были подумать авторы самого React. Сейчас есть много реализаций, но все они — пляски с бубном или как минимум неизвестны широкой публике. И тут у UI-фреймворка опять дилемма — вставлять зависимость на что-то маргинальное? Или ваять свою систему конфигурации?

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


Кстати, connect запихивать внутрь точно не стоит, это лишь увеличит число повторных рендеров.

Да, действительно. connect() на top-level TreeView, а на внутренние memo().

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


В $mol же, разумеется, такой проблемы нет, но кого это волнует.

vintage говорит о полном ререндеринге вашего компонента App, когда достаточно заменить innerText у h1. В шаблоне всё написано (будь он декларативным, этой информацией можно было бы пользоваться), mobx может точно сказать, какие именно данные изменились, но реакт не может этой информацией воспользоваться, а может только перевычислять рендер-функцию заново. Так что такой отличной вещи, как MobX очень не хватает нормальный View.
Приведу другой пример для объяснения. Использование key-prop при генерации html в цикле по значениям массива — это жуткий костыль. MobX может дать информацию, как именно изменился массив и из этой информации и декларативного шаблона однозначно понятно, как менять DOM — добавить/удалить куски или поменять что-то местами.
По поводу key-prop есть такой грешок да, но не вот что прямо трагедия и это не может быть причиной отказа от него.
Key-prop — это частный случай общей проблемы. Сами по себе шаблоны статичны. Если их описывать декларативно, то можно точно сопоставить, какие изменения данных как именно изменяют DOM. Императивно перевычислять куски шаблона и сравнивать их со старыми кусками не нужно. Поэтому хотелось бы видеть к MobX нормальный View-слой, который не будет отбрасывать информацию об изменениях данных, чтобы потом перебирать Virtual DOM в поисках изменений.

И получится KnockoutJS или Vue 1.0. Кстати, в Vue 2.0 они зачем-то же перешли на JSX.

JSX лишь синтаксис по сути.

Только этот синтаксис содержит как подмножество весь js. В результате чего стат. анализ в общем случае становится алгоритмически неразрешимой задачей.

Зато мы имеем тривиальное преобразование JSX в JS, а значит и весь продвинутый тулинг (TypeScript, ESLint, Prettier, etc)

Прошу прощения. Конечно же я имел в виду Virtual DOM в Vue 2

Я не пытаюсь вам что-либо навязать, как по мне, хоть на jquery всё пишите, главное, чтобы мы ни в каком проекте не пересеклись
5 причин, почему вы должны забыть о Redux в приложениях на React

4 аргумента в пользу того, чтобы в следующем проекте вы использовали MobX вместо Redux.

Никаких "отталкивайтесь от задач" или "mobx лучше подходит для таких-то проектов" — это навязывание.

Если честно он лучше во всем, любой кто применял его в бою согласиться

Вы хотите сказать, что все в комментариях к этому посту, кто с вами не согласен — ни разу его не пробовали?

Да, потому что все ссылаются на статьи с якобы проблемами которые на самом деле либо не проблемы, либо решаются элементарно, а не на свой опыт. Только один человек написал о своем опыте на данный момент, который не согласен с mobx

В чем прикол брать redux чтобы подключить ещё 10 библиотек которые упростят работу с ним?

В том, что можно выбрать именно те, которые хочешь и считаешь нужными, а не тащишь безальтернативно целый комбайн спорных/чьих-то чужих решений.

Те которые хочешь? Я уверен что вы руководствуетесь тем, о чем упоминается в статьях и написано в списке требований в вакансиях. А не тем, что вы хотите на самом деле. Просто альтернативы лень попробовать и взвесить именно своей головой плюсы и минусы и сделать выбор.

И как раз поэтому каждый проект на Redux, к которому ты подключился не со старта разработки — это целый комбайн спорных/чьих-то чужих решений. Кто-то обязательно натащит туда модных на данный момент библиотек для упрощения работы с ним. Потому что работать с голым редаксом — боль.

По моему опыту, это, скорее, к React относится. Правда проектов с Redux, но без React я не встречал.

То есть нужно знать ещё хотя бы несколько десятков библиотек, чтобы примерно понимать, чего хочешь?
Я бы согласился с теми людьми выше в комментариях, которые советуют больше думать своей головой. Подключать по каждому поводу библиотеку оказывается часто сложнее, потому что есть какая-никакая кривая обучения, привыкание.
У нас был JS, мы улучшили его с помощью React, который улучшили с помощью Redux, который пытаемся улучшить с помощью множества сторонних библиотек. По-моему, где-то надо остановиться и просто писать код

Нормальным было бы затягивать в апстрим best practices, опробованные коммъюнити в библиотеках. Как это делают в стандарте языка и Babel-плагинах. А сейчас мы имеем:


Хотите иммутабельного состояния — ну нате вам useReducer(). Что, Context API не умеет в селекторы? Ну так оно не для управления стейтом, а для тем всяких там. Механизм подписок не дружит с concurrent mode? Ну извиняйте. Короче, не мешайте нам делать красивые доклады на конференциях, а валите в свой Redux / MobX.

Best practices для кого-то !== Best practices для всех.

Просто непонятно, куда в итоге React развивается. Я с трудом представляю себе достаточно сложный SPA, написанный на голом реакте без state management библиотек. И что же они с этим делают?


Предоставляют более удобные примитивы для облегчения разработки state контейнеров? Нет. Учли наработки community и делают полноценный фреймворк? Нет.


Вместо этого они мутят какую-то свою уличную магию с хуками. А разрабы state контейнеров отплясывают вокруг этого с бубном, чтобы все оно хоть как-то работало.


Да, с хуками стало лучше. Вроде того, что если раньше на голом реакте боль начиналась с 10K строк кода, а теперь только с 20K. Но на 100K все равно нужен state management.

Голый реакт не нужен и не создан для SPA круче todo листа, для этого нужен react + (mobx или redux) + webpack и все это решит абсолютно любую задачу. Вот и все, мне без разницу куда движется реакт, на хуки мне фиолетово(они не несут абсолютно никакой пользы для пользователей MobX) он уже давно приехал в связке со стейт менеджером и решает абсолютно любые задачи.
Нет. Учли наработки community и делают полноценный фреймворк? Нет.

https://reactjs.org/docs/getting-started.html:


React is a JavaScript library

Реакт это не фреймворк и никогда им не станет. И это его преимущество, да. Хотите фреймворков — попробуйте что-нибудь другое?

ура, наконец-то кто-то начал говорить «пора уже перестать думать что Redux + экосистема единственное решение». Спасибо
С той единственной разницей, что код в статьях синтетический и, как правило, то, что лежит в репозитории проекта на 100% отличается от того, что написано в статьях. Ведь рано или поздно к вам придет бизнес и скажет, мы не учли вот эти критерии. После этой фразы вся красота растворяется в «бизнес логике».

Поймите, Redux тоже брали из рассчета, что он решит все насущные проблемы, однако потом вдруг выяснилось, что в рендер компонентов каждый раз прилетает новый объект (даже если данные не изменились), кто-то не уследил за код-ревью и в рендер упал Array.map и был передан в компонент ниже и так далее, а может код ревью не было вовсе?

Как ни крути, при любых условиях код с mobx всегда останется чище и его будет меньше, плюс оптимизация производительности из коробки. Следовательно добавлять/изменять фичи быстрее и проще. А то нервные клетки не восстанавливаются))

Очередная IT-дрочильня. Несколько лет назад большинство яростно облизывало redux, сегодня mobx, завтра вот эти вот «код с X всегда останется чище», «это просто идеально, да и вообще это серебряная пуля» будут говорить про что-то другое. Религиозный фанатизм в IT. А так-то вообще по барабану. Мне тоже больше mobx нравится, но, справедливости ради, redux достаточно просто причесать до вполне вменяемого состояния.

Только если использовать его без его "друзей" и юзать динамические редьюсеры, но это больше костыль и полумера

Любой мало-мальски сложный кейс заставит написать такую тучу костылей, что проще будет работу сменить (похоже все стартаперы по такой схеме сваливают, запилят РоС на мобх и бегут подальше в тот момент, когда нужно уже настоящие задачки решать).

Банальные кейсы, на вскидку, которые решаются на редаксе (или ngrx-store в ангуляр в связке с какими-нибудь эпиками/эффектами) по щелчку пальцев и которые нужно упороться делать на мобх и ему подобных:
  • Анду/реду;
  • Оффлайн мод;
  • Кансел/ретрай кейсы (нестабильная сеть, релогин, прочией кейсы);
  • Лоадинг стейты;
  • Канкарент модификации состояний (реализация полноценной асинхронной работы с приложением с участием нескольких пользователей);
  • Централизованная обработка ошибок;
  • Бонус: отсутствие рейс кондишенов.

Все эти кейсы условно-бесплатные со однонаправленным стейт-менеджером.
Анду/реду

Я бы посмотрел, как вы это реализуете на редуксе. Вы хоть раз реализовывали анду-реду?


Оффлайн мод

Никак не зависит от стейт менеджмента.


Кансел/ретрай кейсы (нестабильная сеть, релогин, прочией кейсы)

Никак не зависит от стейт менеджмента.


Лоадинг стейты

В МобХ из коробки.


Канкарент модификации состояний (реализация полноценной асинхронной работы с приложением с участием нескольких пользователей)

Никак не зависит от стейт менеджмента.


Централизованная обработка ошибок

О чём речь?


отсутствие рейс кондишенов

Любая асинхронноть даст вам рейс-кондишены независимо от стейт менеджмента.

Я никак не ратую ни за R ни за M. Просто за анду/реду реализация кажется простой. И даже есть библиотека с 2000+ лайков github.com/omnidan/redux-undo Хотя там все усложнено. Вобщем идея такая. Хранить массив состояний и индекс текущего состояния. Вот собственно и все. Если сделать это не отдельной библиотекой а прямо в редьюсере то все будет гораздо проще чем в библиотеке

Там всё усложнено не просто так. При реализации анду-реду важно указывать какие экшены влияют на историю (только прямо или косвенно индуцируемые действиями пользователя для внесения изменений). И какие именно состояния трекаются. Нельзя просто так брать и трекать всё подряд по любому чиху.


Ну а в вашей реализации получается ручное вынесение трекаемых свойств в отдельную ветку дерева. Рулить двумя параллельными деревьями (трекаемое и не трекаемое) — такое себе удовольствие.

Нажимаешь undo и получаешь "Saving...", а чтобы увидеть данные нужно ещё раз нажать?

Вы похоже не прочитали что я написал. Я не говорю что всё что я описал зависит от стейт менеджера. Но при его наличии эти задачи решаются практически нативно и без приседаний.
В МобХ из коробки.

Вот это особо повеселило. Это расстановка true/false флажков isLoading, то самое решение из коробки?

Да нет, прекрасно понял. От стейт менеджмента количество приседаний мало зависит. Если не учитывать, что для редукса объём бойлерплейта в несколько раз больше.


Можно подумать в Redux не флажки isLoading будут.

Можно подумать в Redux не флажки isLoading будут.

Как там в мире ту-ду листов и доставок пицц поживается?)

Писаемся в песочнице. По существу что-нибудь скажете?

С помощью редакса это делается через имплеситный кусок пейлоада в экшене, в котором нужно указывать ассоциированный экшен по имени, а в руте стора создаётся глобальный лоадинг стейт вида loaders: Set<string> где string Это наименование потенциально асинхронного экшена (я на ts пишу, в js можно обычным массивом обойтись). И далее везде где нужно вы добавляете этот самый имплиситный кусок пейлоада, обрабатываете его в редьюсере и складываете в лоадинг стейт на уровне префетча данных. После того как произошёл саксесс/фейлур у вас так же неявно этот ключ очищается. Вуаля, у вас 0 строк бойлер плейта, чистый главный стейт и вы можете где угодно понимать идёт ли у вас загрузка нужных вам данных. А уж сделать глобальный лоадер таким макаром вообще проще простого, достаточно проверять что loaders.length > 0 и всё. Если интересно, то как буду дома смогу в коде показать. Или на гисте создать.

Обвязка в виде класса для запросов к АПИ творит любые чудеса, хотите счётчик активных запросов будет писать в mobx, хотите будет автоматически добавлять/изменять isLoding у стора или у компонента, смотря что засуните в аргумент, и т.д и т.п. творчеству нет придела

Через MobX вы можете сделать то же самое, только без этого "имплеситный кусок пейлоада в экшене, в котором нужно указывать ассоциированный экшен по имени", что бы это ни значило.

src/utils/withState.js
export function withState(target, fnName, fnDescriptor) {
  const original = fnDescriptor.value;

  fnDescriptor.value = function fnWithState(...args) {
    if (this.executions[fnName]) {
      return Promise.resolve();
    }

    return Promise.resolve()
      .then(() => {
        this.executions[fnName] = true;
      })
      .then(() => original.apply(this, args))
      .then(data => {
        this.executions[fnName] = false;
        return data;
      })
      .catch(error => {
        this.executions[fnName] = false;
        throw error;
      });
  };

  return fnDescriptor;
}

src/stores/CurrentTPStore.js
import _ from 'lodash';

import { makeObservable, withState } from 'utils';
import { apiRoutes, request } from 'api';

@makeObservable
export class CurrentTPStore {
  /**
   * @param rootStore {RootStore}
   */
  constructor(rootStore) {
    this.rootStore = rootStore;
    this.executions = {};
  }

  @withState
  fetchSymbol() {
    return request(apiRoutes.symbolInfo)
      .then(this.fetchSymbolSuccess)
      .catch(this.fetchSymbolError);
  }
  fetchSymbolSuccess(data) {
    return Promise.resolve();
  }
  fetchSymbolError(error) {
    console.error(error);
  }
}

src/components/TestComponent.js
import React from 'react';

import { observer } from 'utils';
import { useStore } from 'hooks';

function TestComponent() {
  const store = useStore();
  const { currentTP: { executions } } = store;

  return <div>{executions.fetchSymbol ? 'Загружается...' : 'Загружен'}</div>;
}

export const TestComponentConnected = observer(TestComponent);

Вроде полностью соответствует вашему описанию, только в виде объекта с ключами выполняемых асинхронных действий. Это выгоднее, так как позволяет обновлять компонент только при обновлении стейта конкретного действия — если же использовать массив (loaders.indexOf('fetchSymbol') !== -1), компонент будет обновляться и при попадании в этот массив всех других, ненужных для данного компонента стейтов. Хотя в Redux можно сделать селектор для этого. Глобальный лоадер можно завязать на Object.entries(executions).some(([key, value]) => value === true).


Разумеется, на любом движке или ванильном JS можно сделать то же самое, просто на MobX это очень просто и удобно.

Анду/реду;

Много подобного функционала пришлось сделать?
Оффлайн мод; Лоадинг стейты; Централизованная обработка ошибок;

Даже комментировать не буду
Канкарент модификации состояний (реализация полноценной асинхронной работы с приложением с участием нескольких пользователей);

Почему нет? Это вообще мало относится к стейту на фронтенде.
  • Лоадинг стейт

Элементарно, непонятно вообще почему вы этот кейс тут указали


  • Анду/реду

Ничего особенного, решается достаточно просто


  • Кансел/ретрай кейсы (нестабильная сеть, релогин, прочией кейсы)

Так же как и в redux, написал дополнительно логику для этого и вуаля


  • Канкарент модификации состояний (реализация полноценной асинхронной работы с приложением с участием нескольких пользователей)

Как бы mobx не создаёт для этого помех, и ничего для этого предпринимать не надо, опять же не понятно что этот пункт тут забыл


  • Централизованная обработка ошибок

О каких ошибках? В любом случае это решается не на уровне redux или mobx, в них только пробрасываются данные чтобы об этих ошибках оповестить пользователя, тоже пункт не в тему


  • Бонус: отсутствие рейс кондишенов

Пример с проблемой в студию...


Итого, все эти нюансы либо не нюансы вовсе, либо решаются без проблем, и затраты времени на это малы и не сравняться с теми, которые заставляет испытывать redux.


И ещё, если уж так хочется, то можно mobx сделать однонаправленным и имутабельным и при этом все равно будет кода меньше чем с редаксом и оптимизация из коробки никуда не денется.

Элементарно, непонятно вообще почему вы этот кейс тут указали

Элементарно со стейт менеджером. Если это так же элементарно на мобх, поведайте пожалуйста. Ну чтобы настолько элементарно, чтобы не нужно было указывать бесконечных isLoading=true/false или каких-нибудь ещё бойлерплейтных флажков для данных, которые асинхронно загружаются.
реализация полноценной асинхронной работы с приложением с участием нескольких пользователей


Этот кейс я что-то не понимаю. У нас же с приложением на фронте один пользователь работет?

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

Вложу и свои пять копеек.

Во-первых надо думать от задачи, а не от того как выглядит код. А также учитывать какие самые важные базовые принципе заложены и продвигаются библиотекой. В случае MobX это мутируемый стейт и ссылочная целостность. В случае Redux это иммутабельные данные и сохранение истории состояний (аля граф состояний).
Соответственно, если вашей программе нужен Undo/Redo на большую глубину, сериализация состояния и истории того, как приложение в это состояние попало (например, для репортинга ошибок в сервисы вроде Bugsnag или для оффлайн работы), то берите иммутабельные библиотеки вроде Immerjs и Redux в связку. Кстати, заметьте, что Immerjs — это иммутабельная либа от того же автора что создал MobX :)
Если же у вас сложная структура данных с циклическими ссылками, таблицы на миллион строк, чарты с большим кол-вом данных, то есть все что требует сверх высокой производительности то берите MobX.

Второе это то, что обе библиотеки про стейт, а не про работу с асинхронной логикой. Поэтому приводить Redux-Saga или Redux-thunks в качестве недостатка несколько неправильно. MobX может прекрасно работать в связке с Redux-Saga или RxJS или чем-то еще. Вопрос в том на какую архитектуру вы все это положите. Можно, конечно, делать асинхронные вызовы прям из MobX классов, но это приведет ровно к тому почему Facebook изобрел Flux во-первых, и почему Redux победил конкурентов во-вторых. В Flux разделили асинхронную логику и синхронную, пропустив данные через горлышко dispatcher-а. Это позволило контролировать как обновляется стейт и позволило частично избежать гонок и каскадных обновлений, что упрощает дебагинг и контроль состояния. Но оставалась возможность подписывать одни модели на другие с помощью пресловутого waitFor, что часто приводило к тому, что вообще не было понятно кто кого и когда обновил и где спрятался баг. Именно это Redux и исправил сделав стейт предсказуемым. Даже лозунг Redux — «A Predictable State Container for JS Apps».
Поэтому на текущий момент желательно использовать MobX в той же Flux архитектуре и разделять обновление стейта и асинхронные запросы. Что возвращает нас к вопросу а что вообще будет делать ваше приложение и как вам предпочтительнее управлять состоянием?

Из своего опыта добавлю: мы запили на редаксе прекрасную централизованную систему обработки ошибок благодаря middleware и простой сериализации JSON.stringify. При любой ошибке стейт и последнии 5 экшенов логируются в SaaS Bugsnag и всегда можно проверить а в каком состоянии было приложение, что привело к ошибке.

Вы вот так вот прямо приватные данные пользователя логируете в bugsnag?

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

По белому или по чёрному списку?

Доброго дня.


Undo/Redo на большую глубину, сериализация состояния и истории того, как приложение в это состояние попало



тривиально реализуется через autorun. Можно делать глобальные слепки -


autorun(() => snapshots.push(JSON.stringify(store)))

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


autorun(reaction => reaction.observing.forEach(
  ({ name, value }) => changesHistory.push({ name, value, prevValue })
)

Проигрывание этой истории и логирование последних действий при возникновении глобальной ошибки, полагаю, тоже сделать несложно — мне не пригождалось, так как стараюсь решать проблему поиска причины залогированного бага с помощью именованного stackTrace, типизированного error.name и человекопонятного error.message.


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


По теме гонок и каскадных обновлений Redux, как вы правильно заметили, своим диспетчером решает проблему «частично», и мне, к сожалению, приходилось работать в приложениях, где «все остальные части» не решались, и они очень страдали от нагромождения бойлерплейтов и дополнительных библиотек и мидлвар, в которых крайне сложно было создать стабильный флоу. Это не камень в огород Redux, я скорее о том, что проектирование архитектуры никто не отменяет.

Вопрос для тех кто топит за mobx и подобные решения. На юз кэйсах когда нужно сортировать/ фильтровать большой массив данных до сих пор строится громадный граф зависимостей с кучей повторяющихся связей или всё же придумали какие-то вменяемые алгоритмы (перестал активно следить за темой пару лет взад)?

Строится большой граф с кучей не повторяющихся зависимостей. А что не так?

Строится большой граф с кучей не повторяющихся зависимостей.

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


А что не так?

Всё просто замечательно :D

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

В чем вообще проблема выкинуть дубликаты?

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

Не надо делать это в mobx напрямую, в отдельной переменной храни данные, а в mobx пихай только те из них, которые надо отрендерить. Типо mobxStore.items = myBigDataSet.filter(...). Observable тут должен быть только mobxStore.items и все. А myBigDataSet это просто js массив обычный

И нахера тогда mobx? Что-то у меня сомнения в том что вы что-либо делали с использованием mobx :)

Mobx, redux и т.п. нужно только для одного — если что-то изменилось, делаем перерендер, вот и все. Это уже было сказано неоднократно.

если что-то изменилось, делаем перерендер, вот и все

А теперь перечитайте что вы там предлагаете в предыдущем своём комментарии :)

Изменять observable переменную нужными для рендера данными, что не так?

Даже не знаю как вам объяснить :) myBigDataSet и является источником нужных для рендера данных, из него данные могут сортироваться, фильтроваться и использоваться в различных представлениях.

Либо вы прикалываетесь, либо и правда не понимаете для чего нужен мобх реакту и как надо с ним работать.

Так вот пускай на изменение сортировок и фильтров и реагируют представления, если сам myBigDataSet не изменяется или на его изменения реагировать не надо.

Если не надо реагировать на изменения, то нахера mobx? Не пойму что вы тут всё пытаетесь придумать, данные меняются, нужно перестраивать все вычисляемые данные.

MobX чтобы реагировать на изменения в UI типа изменения сортировки и фильтров. Ну, что-то вроде UI к КЛАДР или как там его. По крайней мере это моя трактовка вашей задачи


myBigDataSet и является источником нужных для рендера данных, из него данные могут сортироваться, фильтроваться и использоваться в различных представлениях.

Чего-то вроде и "и сами данные сета могут изменяться" там нет.

Он просто троль, ему без разницы че ему пишут

В чем вообще проблема выкинуть дубликаты?
В том как всё это делать эффективно.

Есть такая структура данных HashSet называется.

Есть такая структура данных HashSet называется.

В том как всё это делать эффективно.

O(1) не достаточно эффективно или что?

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

Ну давайте посмотрим на ваши цифры.

UFO just landed and posted this here
C 5й версии используется нативный Proxy и обвязка создаётся только по необходимости. В 4й на геттерах/сеттерах колличество обвязки линейно зависит от количества данных. То есть да, на ОЧЕНЬ больших данных может начать тормозить. Но например, в последнем проекте около 3000 записей в память при старте кладётся — задержек не наблюдается (MobX 4), при том, что для фронтенда 3000 без пагинации на одной странице нетипично много, хорошая практика такие вещи всё таки на сервере держать. Не знаю сколько нужно загрузить данных на клиент, чтобы положить MobX, мы пока предела не нашли.

Пробежался сейчас по исходникам mobx, они всё же отлавливают повторные обращения во время исполнения, так что да, связи будут расти линейно. Но как же там всё неэффективно реализовано.


Накидал простой пример:


class Item {
  a = 0;
  b = 0;
}
decorate(Item, { a: observable, b: observable });

class Items {
  filter = 0;
  all = [];
  get filtered() {
    return this.all.filter((i) => i[this.filter ? "a" : "b"] === -1);
  }
  click() {
    this.filter ^= 1;
  }
}

decorate(Items, {
  filterValue: observable,
  all: observable,
  filtered: computed,
  click: action,
});

const Store = new Items();
transaction(() => {
  for (let i = 0; i < 5000; i++) {
    Store.all.push(new Item());
  }
})

Смотрю что происходит в профайлере во время исполнения экшена и как он жрёт память и что-то мне не нравится такая производительность и такой оверхэд по памяти. Для большинства задач он конечно же справится нормально, но всё же это не silver bullet.

Сколько памяти сожрано в цифрах? Было до выполнения сожрано, стало после выполнения сожрано, и скажем через 5 секунд после выполнения сколько сожрано

И ещё, какая версия mobx используется?

Разница при исполнении между observable и обычными свойствами у Item — ~650kb. Эффективная vdom либа во время диффа 2500 ДОМ элементов даже не жрёт столько памяти.


Но тут не только проблема в памяти, а в том как реализован алгоритм подписки/отписки зависимостей в mobx, как-то он очень тормозно отрабатывает даже после того как jit разогреется.


И ещё, какая версия mobx используется?

    "mobx": "5.11.0",
    "mobx-react": "6.1.1",

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

Расход памяти ни о чем, можно в расчет не брать

Это только лишь при исполнении экшена, расход памяти на построение новых связей в computed'е. Расход памяти и производительность построения ObservableValue я не учитывал, каждый ObservableValue это слоты на 8 свойств + 1 Set() + динамически генерируемая строка. Если вас это устраивает, то пользуйтесь, меня это не устраивает.


а где цифры в милисекундах говорящие о тормозной работе? Это заняло столько-то, это столько-то и т.д.

В милисекундах относительно чего? Просто так говорить о милисекундах не сравнивая это с чем-то не имеет никакого смысла, можете измерить у себя на машине и сделать выводы.

Цифры на моей машине:


  • React+mobx onClick обработчик (Item с observable свойствами): 6мс
  • React+mobx onClick обработчик (Item с обычными свойствами): 1.5мс
  • vdom дифф с полным перерендером 2500 элементов (dbmonster benchmark): 0.8мс

Вы сравнили react и vdom, mobx к этому не причастен. Тем более 5000 элементов из списка никто в здравом уме не будет пихать в DOM, и ещё человеческому глазу без разницы, 0.8мс или 6.5мс. Такая разница в производительности актуальна только для бэкенда, а не для клиентской части.

Вы сравнили react и vdom, mobx к этому не причастен

vdom дифф в списке просто для того чтоб примерно представлять относительный оверхэд (чтоб понять с какой скоростью на моей машине исполняется такая задача, иначе абсолютные цифры вообще ни о чём не будут говорить)


Тем более 5000 элементов из списка никто в здравом уме не будет пихать в DOM

О чудо, в этом и суть реакта! Посмотрите выступления когда о реакте ещё никто не знал, одна из задач которую решали при разработке реакта — это то что на экране отображается значительно меньше того что находится в памяти, поэтому и отказались от построения fine-grained графа зависимостей.

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

Я специально продемонстрировал пример с фильтрацией. Сортировка и фильтрация — это типичная задача с datatable'ами когда используется occlusion culling и на экране отображается пара десяткой рядов, а в памяти лежит несколько тысяч.

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

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

С чем вы сравниваете по производительности MobX? Если с Redux, то в реальных приложениях MobX бъёт его в разы. Если с чем-то ещё, то прошу вашу чудо-технологию в студию, чтобы нам тоже немедленно начать ей пользоваться. Единственный кейс когда может выиграть Redux это когда у вас мало методов и много данных, относительно простые приложения с массивными клиентскими вычислениями. Как только методов (экшнов, редьюсеров) становится много — Redux начинает дико тормозить, так как каждый экшн обрабатывается каждым редьюсером. При 200 экшнах и редьюсерах тормоза, по моей практике, составляли порядка 200-300 миллисекунд, что уже неприятно заметно глазу. При том, что 200 это не так много, на многих SPA только 200 классов с несколькими методами в каждом. На Redux приложения вроде Google Docs, Excel просто умрут. На MobX будут жить. Да, на трекинг изменений потребляется какая-то память, это осознаное решение, так как память в наше время продолжает расти, а процессоры — почти нет. Положить какую-то часть вычислений в память, мемоизировать что-то из коробки — не такая плохая идея в 2019м.
UFO just landed and posted this here
Какие-то редьюсеры переиспользовались, какие-то экшны обрабатывались несколькими редьюсерами, но в целом добиться переиспользования больше 20% не получалось, большая часть действительно использовалась однократно. Возможно я что-то не понял в философии Redux, буду благодарен за примеры хорошего на ваш взгляд кода, эффективного, читаемого и гибкого. В моём случае, с Redux, код неизбежно скатывался в яму либо по производительности CPU, либо по производительности программиста.
UFO just landed and posted this here
Обычный switch получался примерно по 5 действий, больше не делал, так как хотелось single responsibility у каждого модуля. Плюс вложенность некоторая была, рекомендуют один уровень, но это как-то совсем грустно, переиспользование страдает и читаемость, так что до 2-3 доходило от корня. То есть в сумме около 40 switch на каждый action. Плюс некотоорые action другие вызывали, чтобы не дублировать логику, например инициализация — запросить такие, такие и такие данные. Переход на другую страницу — инициализировать это и это, если не инициализированно. Можно было бы, например, размазать инициализатор по редьюсерам, добавить экшн напрямую в свичи, но тогда снизилась бы читаемость, единственный способ узнать что происходит при инициализации стал бы поиск по всем редьюсерам. Можно было бы не вызывать другие экшны, а дублировать в инициализаторе, вред этого и так понятен. Мы выбрали не заниматься преждевременной оптимизацией, а писать так, чтобы было понятно и логично, но Redux не согласился.
При том, что большинство действий довольно типовые:
— запросить данные с сервера
— немного преобразовать
— присвоить в стор, чтобы отобразить
В MobX решается одним фетчем, одной чистой функцией декоратором и одним нативным присваиванием. В redux, тоже есть fetch (правда с довеском из saga, thunk и т.п.), тоже чистая функция декоратор, только вместо присваивания какая-то дичь с иммутабельностью, не к месту применённому паттерну «команда» и другого барахла, которое я не заказывал. Говорят легко тестировать, только тесты получались однотипные, проверить что action-creator создаёт правильный объект (ни разу не сломались), или что switch правильно отрабатывает и возвращает правильный ответ (только чистые функции и без redux прекрасно тестируются, в MobX тоже).
По производительности, да, можно написать свой combimeReducers, можно заменить switch на хеш или сравнение строк на числовые константы или лучше битовые маски, можно генерить весь бойлерплейт на каком-нибудь yeoman, можно много чего улучшить, только это будет уже не redux, а самописное решение на основе проблемной технологии. Новый человек, когда придёт, будет какое-то время изучать особенности вашего кастомного оптимизированного redux. Хорошая библиотека должна иметь простой интерфейс, и скрывать сложное и умное решение, которое долго писать самому (это как раз MobX), а плохая библиотека имеет сложный интерфейс, с кучей условий и органичений, и простую внутренность, которую легко и самому написать (узнаёте?).

Субъективный нетехнический плюс MobX — он хорошо ложится на классические практики DDD в частности и ООП в целом. На бэкенде они применяются чаще ФП и в случае MobX гораздо проще добиться понимания между фронт и бэк частями команды, да и бизнесу проще обычно оперировать сущностями с поведением чем иммутабельным стейтом с редьюсерами. А фуллстеку не нужно десятки раз за день переключаться между парадигмами.

UFO just landed and posted this here

Два основных "формата" данных, которые надо обрабатывать в обічніх бизнес веб-приложениях: формы UI и записи в РСУБД. Оба предполагают некую контекстную собранность. Оба предполагают мутабельность. ООП служит хорошим связующим звеном между ними и при этом хорошо ложится на схемы бизнес-процессов.


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

Мы с коллегами тоже задумались над этой проблемой, наше решешие — библиотека react-hoox, которая родилась нашими стараниями.
Бойлерплейт отсутствует, один маленький хук и любой объект становится реактивным.
Через какое-то время мы напишем полноценную статью на хабре про нее. А пока в тестовом режиме собираем фитбек и пробуем в продакшене

Нашел на гитхабе, использовать я это конечно не буду)

Один из главных критерий хорошо бы написать про способ организации этих сторов

Любой способ который захочешь или который согласуете внутри команды. Кому что по душе.

Аргумент про setState вообще ни к селу ни к городу на редаксе его тоже не используют потому что OSOT и т.п.

На редаксе его используют, когда не хотят засорять глобальній стор локальными данными. Две альтернативы. Редакс же не позволяет создавать локальные сторы? Или позволяет, но это очень геморрно? С MobX есть третья простая альтернатива: просто observable объект вне глобального стора. Хоть вне компонента, хоть внутри его как полная замена стейта

Спасибо за статью, и комментарии тоже отличные. У меня была потребность переделать один большой проект. Выбрал Redux, уже занёс руку над Enter'ом. В самый последний момент чёрт дёрнул залезть на Хабр. В итоге начал пилить на Mobx

MobX (без дополнительных расширений), насколько я понимаю, решает одну проблему: точечное (и поэтому эффективное) обновление UI без перерендера всего дерева.


Redux решает проблему структурирования бизнес-данных приложения и управления состоянием, но в части обновления UI он работает неэффективно, полагаясь на эффективность рендера Реакта (зря) и некоторые простые оптимизации (тоже зачастую нерабочие).


Идеальным было бы (ну, по крайней мере, для меня) совмещение этих двух подходов. Возможно, именно этим и занимается mobx-state-tree.


В плане управления и структурирования состояния для меня Redux вполне норм, если обмазаться хелпер-либами (но это изнанка простоты редакса). В плане оптимизации рендеринга Редакс ужасен.


Было бы круто их совместить, но без заморочек MobX типа описания классов для всего.

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

А это как раз наиболее полезная часть, потому что позволяет совместить в одном месте объявление типа (которое нужно чтобы не запутаться самому) и реализацию хранилища (ради которой все и затевается).

Если использовать тайпскрипт, то эти описания, зачастую, — двойная работа, тк все-равно нужно описывать результат ответа АПИ. И совместить их не всегда возможно.

Если вы решили хранить ответ API целиком — то вам вообще не требуются классы, простого observable.ref более чем достаточно.


Если же предполагается какая-то обработка — без отдельного типа не обойтись.

Тогда же и точечного обновления не получится?

На самом деле, никто не мешает для MobX завести не несколько хранилищ, а одно, как в Redux. Пару проектов делал именно в таком ключе. Один на MobX, другой по сложнее на mobx-state-tree.


Правда, бизнес логику нужно будет вывести в отдельный слой сервисов. А стор тогда получится вырожденным. Просто хранилище реактивных данных. И не нужно никаких отдельных классов. Да, в таком варианте мы несколько жертвуем эффективностью. В случае классов мы можем выборочно навешивать @observable. А тут реактивным будет все. Но в Vue же как то живут с этим и не жалуются.

@observable должны быть все классы реакт компонентов, иначе весь профит теряется + непредсказуемое поведение, и вложенные компоненты без @observable могут не реагировать на изменения даже пропсов

При чем здесь реакт компоненты? Я же говорю о выборочных @observable на разные поля класса модели. А не об @observer на компоненты.

Пардон, глаз замылился я прогнал)))

MobX вполне решает проблему структурирования бизнес-данных приложения и управления состоянием. Вернее он не мешает её решать стандартными средствами JS/TS. Вся, ну почти вся, мощь ООП к вашим услугам.

Redux vs MobX — это вторая инкарнация противостояния концепций dirty check (теперь — с иммутабельностью) vs dependency graph (новинка — декораторы).


До этого был AngularJS vs KnockoutJS.


Преимущества и недостатки концептуально остались те же самые:


Dependency graph:
Pros:
  • точечное обновление, соответственно быстрая и оптимальная перерисовка UI

Cons:
  • нужно описывать модели
  • проблемы с сериализацией и десериализацией
  • изначальная загрузка данных в модели небесплатная, из-за необходимости построения графа зависимостей
  • ад с вычисляемыми свойствами и зависимостями зависимостей
  • сложная кухня внутри, связанная с тем, что вычисления не должны зацикливаться и не пересчитываться по несколько раз
  • невозможность использовать стандартные структуры данных (массивы, хэши и сеты, и т.п.). Возможно, сейчас ситуация и поменялась, но я не вижу, как бы это могло произойти

Dirty checking:
Pros:
  • Работает с любыми данными, работает со стандартными структурами данных
  • Можно использовать селекторы и любые преобразования данных
  • Не требует специальных действий для сериализации и десериализации
  • Проще и предсказуемее, за счет отсутствия скрытых зависимостей и сложной внутренней кухни
  • Есть возможность небольшой оптимизации при помощи сравнения по ссылкам для больших деревьев, если часть дерева не поменялась

Cons:
  • Не реактивно (требует внешнего события для обновления UI), впрочем в редаксе это не проблема, т.к. есть естественные события в виде диспатча экшенов.
  • Потенциально медленнее

Что выбрать? На мой взгляд, оптимально использовать dirty checking для большинства мест. Для сложного UI с редко перегружаемыми данным (типа динамических графиков), где важна оптимальность обновления UI — можно использовать MobX или подобное.

Как-то вы вот так взяли и запросто походы AngularJS и Redux приравняли. А ведь там все совсем по разному устроено...


ад с вычисляемыми свойствами и зависимостями зависимостей

Где?


невозможность использовать стандартные структуры данных (массивы, хэши и сеты, и т.п.)

С появлением Proxy это все уже неактуально. Да, для каждой структуры нужен свой реактивный эквивалент, но написать его — не проблема.

Настоящий ад начинается если внутри ручных подписок (intercept/observe/autorun) на одни реактивные данные, изменять другие реактивные данные. Но за такое надо бить по рукам.

1) Вместо autorun более безопасно использовать reaction.
2) Эти вещи сразу же выявляются в консоли, MobX об этом говорит.
Reaction doesn't converge to a stable state after 100 iterations. Probably there is a cycle in the reactive function

И когда разворачиваешь ошибку в стэк трейсе видно на какой именно реактивной функции он спалил это.

Ну а $mol_atom2 при попытке изменить состояние, от которого уже зависит текущее вычисление кидает исключение, а не перезапускает его по 100 раз.

Не для 100% это правильное поведение кидать сразу исключение, так что, это не в плюс вашему молу)

Например, в каких случаях это неправильное поведение?

Сходу пример в голову не лезет, но по любому надо дать шанс на сколько-то итераций

Зачем? Изменение своей же зависимости — логическая ошибка.

Речь не об изменении именно своей зависимости, а о цепочке, один меняет зависимость второго, а второй первого

Да без разницы. Это попытка вытянуть себя за волосы.

Так или иначе MobX эту херню сечёт и говорит, об этом

Ага, спустя 100 бессмысленных итераций в течении которых всё может измениться до неузнаваемости и ищи потом концы. Для примера, как сейчас выглядит исключение в $mol_atom2:


Uncaught Error: Doubted while calculation 

$my_app.title
$my_app.title/1:$my_app.print_title()

    at $my_app.title.doubt (atom2.ts:206)
    at $my_app.name_full.doubt_slaves (atom2.ts:228)
    at $my_app.name_full.obsolete (atom2.ts:187)
    at $my_app.name_first.obsolete_slaves (atom2.ts:221)
    at $my_app.name_first.push (fiber.ts:223)
    at $my_app.name_first.put (atom2.ts:127)

Тут сразу видно, что свойство title запустило экшен print_title, который изменил name_first, от которого уже есть транзитивная зависимость через name_full.


Да, надо причесать вывод ошибки, чтобы было наглядней. Однако даже в таком виде она куда полезней, чем "Мы тут 100 раз гоняли вычисления, подвесив вкладку на 10 секунд и похоже попали в бесконечный цыкл, вот вам стектрейс, разбирайтесь сами, где накосячили".

1) Он ничего ен вешает, а прекрашает реакции.
2) Он говорит где именно он заметил косяк
  1. Если одна итерация занимает 100мс, то 100 итераций занимают 10с.


  2. Обратите внимание на два стектрейса, помогающие понять как так получилось, что образовался цикл. МобХ же судя по коду выводит лишь реакцию, в которой обнаружил проблему. Но причина проблемы может быть глубже.


Если одна итерация занимает 100мс, то 100 итераций занимают 10с.

Если у вас итерация занимает 100мс, то это значит, что приложение тормозит на уровне невозможности пользоваться им.

Вот вы не уточнили итерация чего и в каком приложении, а уже поставили диагноз. Не надо так. Много ли вы знаете приложений инициализация которых занимает меньше 100мс?

Много ли вы знаете приложений, инициализация которых выполняется в цикле?

Не надо так. Много ли вы знаете приложений инициализация которых занимает меньше 100мс?

"Итерация" — это то, что регулярно повторяется, так что инициализация точно не подходит. В данном случае из контекста совершенно ясно, что это вычисления на каждом рендере.
Перечитайте предыдущие посты, вы потеряли контекст обсуждения.

Вам, конечно, виднее, что я с MaZaAa обсуждал.

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

Я говорю даже не о циклах в обработке реакций. Это уже за гранью добра и зла.
Даже если программа написана корректно: изменение одного observable порождает одну реакцию, которая изменяет другой observable, который запускает другую реакцию, которая...


Это нарушает саму парадигму реактивности. Это нарушает линейность кода. Это получается какой-то уже event-driven подход. То, от чего мы все стараемся уйти.

А в чем собственно проблема? Ну отреагировали они все, так и должно быть, они ведь реактивные. Да бывает в некоторых кейсах, надо подумать головой, например:

Проблема:
Реакция #1) Реагируя на смену page мы должны дернуть API
Реакция #2) Реагируя на смену filterParams, мы должны page сделать равной 1 и дернуть апи.

Вот в реакции #2 уже началась проблема, тут дернется АПИ и в реакции на смену page тоже дернется АПИ.

Вариант решения + игнор неактуальных запрос в случае быстрых кликов юзером:
Реакция #1) Реагируя на смену page мы меняем объект apiParams.page = page
Реакция #2) Реагируя на смену filterParams, мы меняем объект apiParams.page = 1 и apiParams.filterParams = filterParams
Реакция #3) Реагируя на изменения apiParams мы инкрементим counter, далее дергаем АПИ с нужными параметрами, далее проверяем, если counter совпадает с нашим, по обновляем данные для перерендера, а если нет, то ничего не делаем.

Можно конечно поставить ещё и { delay: 300 } на реакцию #3 чтобы невелировать бешеную активной пользователя) и т.д и т.п.

Ну вот это как раз и является неявным поведением.


Почему бы сразу не завести отдельный action:


@action setFilterParams(filterParams) {
  this.page = 1;
  this.filterParams = filterParams;
}

А неактуальные запросы можно вообще отменять с помощью AbortController.

Смотрите, что можно делать благодаря абстрагированию асинхронности:


@ $mol_atom2_field
get users() : User[] {
    const uri = `/users?filter=${ this.filters }&page={ this.page }`
    return $mol_fetch.json( uri )
}

При любом изменении filters и/или page происходит ровно один запрос к API. Более того, если уже есть активный запрос, то он отменится автоматически.

Ангуляр (первый) использовал dirty checking (сравнение объектов) по данным. Реакт использует dirty checking по виртуальному ДОМ. Редакс использует упрощенную систему, сравнивая объекты только по ссылке, хотя во время оптимизации часто переопределяется функция сравнения.

Redux Cons:


  • сложнее понять модель предметной области
  • ограничения на типы данных, в частноти нельзя использовать Set/Map
  • частая необходимость эмуляции графов чем-то типа реляционных связей или огромная избыточность, причм ещё заставляющая использовать сравнение по значению. Хорошо если не глубокое.
  1. Да, отсутствуют описания классов модели. Я использую тайпскрипт, поэтому такой проблемы нет.
  2. Можно, но нужно их копировать при изменении, либо использовать иммутабельные аналоги
  3. Да, есть такое
  1. Ну опишите вы типами или интерфейсами шейп, аналогичный свойствам моего класса. Ну селекторы будут возвращать этот тип. Но данные только часть модели. Как понять, что диспатч какого-то редьюсера изменит нужный мне "класс"? Особенно если, как у многих принято, редьюсеры в отдельной паппочки, селекторы в другой, а типы, наверное, в третьей (ни разу редакс+тайпскрипт не видел).
Как понять, что диспатч какого-то редьюсера экшена изменит нужный мне "класс"?

Find usages… по интерфейсу стейта, находим все редьюсеры, в коде смотрим, какие экшены проверяются.

Ну, вариант. конечно, может даже работающий, но удобство так себе, по-моему.

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

Можете пояснить, какие затраты имеете в виду? Модель данных в MobX – это мутабельный объектный граф. Он уже денормализован "из коробки".

Если у нас граф объектов с глубокой вложенностью, обновление большой ветки графа должно привести к пересчету всех его зависимостей (в том числе и UI). Потенциально это может быть медленно: отписаться от старых данных, обновить данные, пересчитать зависимости.


Поддерживает ли MobX инфо о том, какие зависимости у детей той или иной ветки графа?

Что будет, если полностью перезаписать значение prop1?

На самом деле здорово. Как они, интересно, отслеживают изменения по всему дереву?


Поигрался немного с вашим примером https://codesandbox.io/s/wonderful-pond-gtcmx


Действительно, рендерит только то, что нужно.

Как они, интересно, отслеживают изменения по всему дереву?

Подпиской на изменения в момент обращения.

Ну как? MobX нормальная тема? или Redux forever?)

Нормальная тема, с точки зрения производительности рендеринга вообще супер, но мне нравится подход редакса к организации и структурированию приложения.


Но, получается, если MobX хорошо работает с агрегатами, на его основе можно сделать эффективный стор для редакса? Селекторы сделать на основе @@computed. Без лишнего копирования и с точечными изменениями (ну а если нужно, то можно и заменять части стора большими кусками, при записи, например, ответа АПИ).


Или не все так радужно будет?

Можно, и оно даже будет работать.


connect для mobx можно без особого труда написать самостоятельно: https://habr.com/ru/post/457250/#comment_20316574


В качестве стора можно выбрать mobx-state-tree (MST).


Кроме того, в MST есть функция asReduxStore, которая позволяет мимикрировать под redux.

А вам не кажется, что этот connect слегка нечестный?


<Observer>
  {() => <WrappedComponent {...mapState(store)} {...props} />}
</Observer>

Тут <Observer> подпишется только на изменения shallow props из {...mapState(store)}. Вот пруф.


И получается, у нас во-первых внутри <WrappedComponent> будут по-прежнему использоваться MobX-объекты вместо Plain. А во-вторых, он не будет реагировать на изменения deep props в переданном ему объекте, даже если они затронуты рендерингом.


Чтобы этого избежать, нужно в mapStateToProps() вызывать mobx.toJS(). А это уже убъет саму идею: подписка пойдет на все свойства, вместо затронутых. И объекты будут пересоздаваться каждый раз с помощью deep copy, а не shallow как в Redux.


Так что на самом деле mobx-state-tree asReduxStore() единственный вариант для такого кейса.

Ну тут одно из двух. Или передавать plain-объекты, или делать Widget тоже обсервером.


Во втором случае все будет работать из коробки, в первом надо следить за типами объектов.

Насколько я понимаю, MobX оборачивает каждый объект в графе в прокси. Хорошо подходит для случаев, когда данные загружаются однажды и редко меняются (ну в принципе любой CRUD и вообще большинство вариантов использования). Для статических частей стора (типа статических гридов) достаточно сравнивать объекты по ссылкам (как в классическом редаксе).


Для часто перезаписывающихся данных (типа отображения обновляемых данных с сервера) это подходит хуже, т.к. нужно опять же проксировать всю иерархию, и это все-равно приведет к перерендеру всего поддерева UI.

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

Вот даже кто-то написал эту функцию github.com/jeremyhewett/mutate-object

MobX отлично справляется с часто меняющимися данными без перерендеринга всего поддерева. Главное — не использовать immutable подход как в редаксе, а именно менять данные. Пришла новая версия объекта User с id = 1 — не создавать новый объект, а пропатчить существующий. Отрендерятся только те компоненты, которые зависят от реально изменившихся свойств (если деструктуризировать в пропсы нормально, соблюдая правило передавать в observer компоненты через свойства observable объекты, настолько малые насколько возможно, но не доводя до примитивов, то есть предпочиать <UserAvatar user={user}/> а не <UserAvatar url={user.avatarUrl} />

Так с MobX вы можете данные структурировать точно так же, можете с ним вообще все что угодно делать, как душе угодно будет

Присоединяюсь к MaZaAa. MobX вообще пофиг на вложенность. Можно использовать даже не дерево, а циклическую структуру данных.


Все подписки точечные (только на те поля, которые были затронуты). Все изменения данных тоже точечные. В отличие от Redux, при изменении вложенного поля, здесь не копируются объекты по пути к этому полю. Поэтому и компоненты, подписанные на вышестоящие объекты, не перерендерятся.

Есть у нас две сущности Contract со свойством customer типа Person. В РСУБД или редаксе это, скорее всего, будет, столбец/поле customer_id в таблице/массиве/хэше contract и отдельная таблица/хэш person, чтобы избежать дублирования. Но в результатх выборки этих договоров определить что это один клиент можно только по ид. В графовой/объектной СУБД, чистом JS или MobX это скорее всего будет свойство customer, ссылающееся на один объект Customer. И что это один клиент можно определить сравнениями как ид, так и ссылками. Ссылками быстрее и естественней.

И откуда вы возьмете ссылку на уже загруженный объект Customer без нормализации?

Не очень понимаю, что тут значит "без нормализации", При обраотке десериализованных денормализованных данных поля customer смотрим в коллекции уже загруженных Person по id. Если есть, отдаём для customer его, если нет — создаём новый, добавляя в коллекцию. В общем IdentityMap

Redux — функциональщина которая понятно как работает, mobx — какая то магия. Redux создает каждый раз новый экземпляр stor'a — поэтому можно отследить в reduxDevTools историю изменений stor'а. Большинство проектов и библиотек работают с Redux — проще интергрироваться при необходимости.
Большинство проектов и библиотек работают с Redux — проще интергрироваться при необходимости.

Только если эти библиотеки написаны для Редакс
Redux создает каждый раз новый экземпляр stor'a — поэтому можно отследить в reduxDevTools историю изменений stor'а.

Можете подсказать зачем это надо?
Redux — функциональщина которая понятно как работает, mobx — какая то магия.

Тогда проще писать на ванили.
можно отследить в reduxDevTools историю изменений stor'а

Мой коллега сказал, что на одном из проектов ему это очень помогло понять, что вообще происходит. Только вот это аргумент не в пользу Redux =) Потому что чтением исходников это понять было на порядок сложнее.

Redux — функциональщина

Нет.

UFO just landed and posted this here
Если вы ещё раз внимательно почитаете, то увидите, что это не мои домыслы, а краткий перевод статьи с английского. С некоторыми моментами из этого источника я абсолютно согласен. Если вам нравится setState, используйте его на здоровье.
Вы придрались к мелочам, но самую соль либо не оценили либо опустили, вот самый наглядный пример MobX vs setState, не берем в расчет маловероятные кейсы из-за асинхронного setState'a:
codesandbox.io/s/cranky-voice-ngv5e — setState проблема
codesandbox.io/s/epic-mcclintock-o3q18 — MobX этой проблемы нет

P.S. надо смотреть в консоль
И что? Вы считаете, что это просто так сделано? В первую очередь это необходимо для того, чтобы ваш браузер на зависал на жирных перерендрах

Чтобы браузер не зависал на жирных перерендерах — достаточно сделать асинхронным рендер (как оно и сделано). Не вижу причин по которым setState тоже должен быть асинхронным.


Выглядит элегантно и удобно, но странно хранить данные компонента в глобальном объекте и загружать его тем самым. [...] И опять, это анти-паттерн, потому что state является инкапсулированным и приватным согласно парадигме React.

Это аргумент против redux. В MobX же нет на это никаких ограничений, можете хоть глобальные объекты использовать, хоть локальные свойства, хоть что-то среднее.

Если посмотреть примеры выше, то можно увидеть, что в случае с MobX я не использовал pure component и это не ошибка. Вам просто не надо использовать никакую оптимизацию в этом случае, потому что ваш компонент будет перерендериваться только тогда, когда данные, которые вы в нем используете поменяются.
В Redux совершенно аналогично — компонент, обёрнутый в connect(), будет перерендериваться только при изменении данных в store, на которые он подписан (или при изменении «внешних» props).

Иными словами, connect() реализует внутри себя shouldComponentUpdate(), который выполняет проверку props'ов на shallow equality, так что использование PureComponent для обёрнутого в connect() компонента абсолютно бессмысленно (и даже вредно).

Подробнее можно прочитать в документации.
Накидал 2 простых примера, которые показыват на практике кейс, который я описал:
1. С редаксом, при клике вызывается экшен и страница перерендеривается, хотя на ней нечему обновляться: codesandbox.io/s/reactredux-70hm1
2. С мобиксом, при клике компонент не перерендеривается, потому что на странице нечему меняться: codesandbox.io/s/mobxreact-s7db5

В идеале конечно такого не должно происходить, я должен был убрать это свойство из mapStateToProps, но в данном случае MobX мне простит ошибку, а Redux — нет.

А, ну в таком случае, конечно, будет перерисовка — компонент через connect() подписан на обновление state.value, хотя он ему не нужен.


Но это совсем другая история — PureComponent тут тоже никак не поможет и он, опять же, в данном примере совершенно лишний.

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


Некоторые соображения:


  1. На практике такой сценарий достаточно редкий. Подписываются на store обычно т. н. controller-view (а их ограниченное количество), которые полученные из store данные спускают вниз дочерним pure-компонентам. Соответственно, controller-view сам никакого UI не создаёт и его перерисовка максимально дешёвая.
  2. Ошибки подобного класса весьма хорошо выявляются статическим анализом.
  3. Если закрыть глаза на пп. 1 и 2, то остаётся reconciliation. Да, это недешёвая операция, но, насколько я понимаю, "реактивная магия" MobX тоже отнюдь не бесплатна. Пожалуй, было интересно увидеть честное real-world сравнение производительности подходов Redux и MobX, но вряд ли мы такое когда-нибудь увидим. :)
  1. ToDo это всё же далеко не real-world пример.
  2. Не знаю почему, но у меня результаты скачут в несколько раз (сделал несколько прогонов).

Articles