Комментарии 478
А хотите ещё в 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 каждому тегу". Это не защищено от дурака и от глупых опечаток, и просто неудобно.
Просто наберитесь опыта в реакте и такие вопросы, проблемы и желания у вас врятли вновь появятся
От опечаток защитить не сложно.
И наличие человекопонятного идентификатора у каждого элемента — это дико удобно, на самом деле. У них просто тьма полезных применений.
Как гарантировать уникальность и читаемость одновременно?
Алгоритм примерно такой:
- При старте рендеринга в глобальные переменные запоминается текущий guid и пустое множество для локальных имён.
- Для каждого тега проверяется есть ли указанный в пропсах id. И если есть, то кидается ошибка.
- guid из глобальной переменной склеивается с локальным id — получается guid для конкретного тега.
- Если в доме есть элемент с аттрибутом id = guid, то берётся он, иначе создаётся новый и ему проставляется этот аттрибут.
То есть только в рантайме и только когда дойдёт очередь до конкретного рендеринга? Причём где допущена ошибка понятно не будет, выскочить на том компоненте, который рендерится вторым по флоу приложения, а не на том, который был создан вторым по флоу разработки?
Как-то я не понятно алгоритм объяснил. Лучше глянуть код: https://github.com/eigenmethod/mol/tree/master/dom/jsx
Вполне себе удобно получилось:
https://github.com/eigenmethod/mol/tree/master/jsx/make#usage-example
Скажите, а как? Я ни разу не фронтендер (ладно, я был джуном, но завязал), потребовалось тут сваять что-то не очень сложное, но весьма интерактивное. Наткнулся на эту статью как раз в поисках того, на чем пилить. Если вы советуете выпилить реакт — я с радостью приму ваши советы. Надеюсь, это не jquery, ибо портянки.
А как вы делаете серверсайд рендер без наличия общего слепка состояния приложения, которое позволяет делать подход в случае с редаксом?
Вы делали SSR на MobX?
Кстати делал на основе другого проекта reactjs + redux выпиливанием 2/3 кода стора. Перед тем как взял в работу effectorjs процентов 10 сделал на mobx. Конечно у riotjs более открый интерфейс в то время как в react даже redux ходил по недокументированная ранее api.
Собственно, мой вопрос заключается в следующем: для полноценного SSR необходимо грузить стейт на сервере, рендерить страницу, а потом стейт сериализировать в страницу (в тег script обычно).
Судя по архитектуре MobX, в котором модели являются классами, моделям требуется специальный сериализатор.
Поддерживают ли модели MobX сериализацию?
Это зачем такие извращения? Сср нужен лишь для роботов, что не умеют в яваскрипт.
Потом где то год назад появились гайды для веб разработчиков где была показана схема с prerender. То есть как бы подтверждение того что читать они могут но скорее всего не хватало мощностей чтобы делать это для всех сайтов без исключения. И вот теперь недавно появилась серия интервью почему то с распитием коньяка где пропагандируется отказ от ssr и prerender. Я честно говоря воспринял это как то что google теперь гарантированно и 100% читает яваскрипт и решил наконец окончательно стать единственной поисковой системой тк другим для чтения яваскрипт придется на два порядка увеличить мощности по железу что не все поисковики выдержат финансово.
Но пока ещё пользователи ищут не только на google. Кроме этого яваскрипт может быть тяжел для работы на слабых девайсах и не читаться скринридерами.
Хотя я сталкиваюсь с игнорированием этих вопросов разработчиками. т.к. хочу ангуляр/реакт/вью и не хочу ничего больше знать и ни о чем больше думать.
А есть примеры использования graphql + Apollo вместо redux или стора в принципе?
Идея с единым местом, хранящим всё состояние приложения, очень помогает при разработке изоморфных приложений. На mobx можно сделать подобное, с помощью mobx-state-tree, который здесь уже посоветовали, но тогда вы получаете почти тот же самый редакс.
При чем тут state-tree? Просто mobx тебе позволяет делать единое место хранения и ещё много всего, никак не ограничивая тебя и не связывая руки.
Ок, а можно статью на эту тему, или пример кода, где это единое состояние хранилища генерируется на сервере, далее передаётся на фронтенд, чтобы первичная его инициализация произошла с уже с сгенерированными сервером данными, и тот хтмл, который отрендерился сервером, не приходилось рендерить повторно на клиенте и не приходилось повторно подгружать данные с бекенда, необходимые для этой страницы? Собственно, что и является полноценной реализацией изоморфного приложения. На mobx, конечно же.
Я напишу статью когда вернусь из отпуска, с телефона это будет сделать нереально. А вообще я писал уже комментарий по этому поводу с подсказками, что надо сделать чтобы работало так, как вы хотите. Можете его прочитать и ветку с этим комментарием и может у вас все встанет на свои места если уже юзали mobx
А как вы делаете серверсайд рендер без наличия общего слепка состояния приложения, которое позволяет делать подход в случае с редаксом?
А какой вам при SSR нужен стейт кроме, с-но, самого роута?
Данные которые дёргаются от АПИ, что бы отрендерить например страницу со списком товаров
То есть если на клиенте для управлением состояния используется стор то он же должен быть заюзан и на сервкрк
Не понял, зачем? состояние в роуте, с-но просто рендерим с роута, как обычно. Где проблема возникает?
Если реакт использовать для чисто серверного рендеринга где все страницы рендерится на сервере то сторинеинужен
А так ли нужен этот серверный рендеринг?
Только если seo оптимизация критична, у меня тьфу тьфу таких проектов на реакте не было и не будет (обычно это интернет магазины и прочее не интересеое)
Теперь я нажимаю клавишу F5 и загружаю страницу с сервера.
Ну и пусть так же вызывается getCategiries и грузит данные. Не понимаю, в чем проблема возникает. Код-то один и тот же.
Проблема которая послужила началом этого треда состояла в том что mobx не имеет встроенного метода для десериализации объекта и как я понял по коду примеров с сериализацией там тоже не все хорошо в части циклических обьектов
Можно просто написать свое решение и все, там на самом деле все достаточно просто, подсказка: сериализатор и десириализатор + функция которая будет вызываться в конструкторе каждого класса mobx которая будет восстанавливать его состояние и будет счастье с минимальным вмешательством в архитектуру и в целом в код
Если реакт использовать для чисто серверного рендеринга где все страницы рендерится на сервере
Тогда и реакт не нужен
А вот в том же Razor (asp.net) есть не только поддержка типов — но и поддержка метамодели, которая позволяет провести как клиентскую валидацию, так и серверную, на основе атрибутов (=аннотаций) свойств.
И для простых CRUD эту же валидацию можно частично продублировать на стороне СУБД средствами Entity Framework.
Вообще как удобно так и можешь делать, никаких ограничений и навязывания что и как надо делать
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)
}
Для меня норм, когда стор хранит данные и отвечает за их преобразование при обращении к нему. На мой взгляд, не стоит совмещать в сторе работу с данными и отправку запросов на сервер.
Единого правильного пути нет, кому как удобнее и кто как считает более приемлемым
Вроде бы и не поспоришь. А начнёшь спорить то будешь уличен в нарушении SOLID. Но как то все это подозрительно из простой задачи превратилось в какие то джунгли
Как я тебя понимаю, к великому сожалению такая жесть преобладает
А начнёшь спорить то будешь уличен в нарушении SOLID.
Это нарушение не SOLID, а квадратно-гнездового принципа. В реальном приложении у Вам в каждом из модулей будет нужно выделить отдельно либо контроллер, либо какой-то сервис, либо репозиторий. Проблема в том что, если вы будете выделять в каждом из модулей только нужное у Вас получатся сильно разнородные модули.
Чем плоха разнородность? Новый человек потом на проекте будет долго искать куда подставить свой код. Потому что вчера он делал тот же функционал логирования метрики и правил что-то в контроллере модуля, а у этого модуля контроллера нет, ну зато репозиторий есть, поставлю там, в данный момент все тоже самое. Потом контроллер появится и логгирование метрики станет работать некорректно.
Поэтому зачастую проще сразу накидать стандартную структуру содержащую все возможные этапы обработки запроса/жизненного цикла или чего-то еще, чем потом на ходу пытаться их выделить.
Возможно Вы думаете, что большинство таких контроллеров/сервисов/репозиториев — не занятых ничем кроме проброса значения дальше, это какое-то отражение внутренней логики. Нет, это тупо форматирование. Как скобочки — форматирование для файла, так и эти тривиальные классы — форматирование для проекта. Возможно ху… е, возможно стоило сделать нормальное дефолтное поведение, но главно это одинаковое на всем проекте.
Лучше так, как вы написали, не делать...
Во-первых, при наличии таких операций как 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 для внутреннего состояния компонента
MobX также увеличивает кривую обучения через Observables, которые так и не стали частью языка.
На своем пути к минимализму я остановился на связке useReducer() hook + Context, которые идут «из коробки» и успешно заменяют мне 90% функциональности Redux.
Уделить 1-2 часа доке MobX, react-mobx и поиграться с ними и все, считай что ты ас и не надо никаких хуков и прочей нечисти, чистые функции так и останутся чистыми.
Observable от mobx не имеет никакого отношения к указанным вами observable
Совсем никакого</>?
Вся асинхронная логика может реализоваться на чистом Redux через разные уровни Middleware.
Я вам открою секрет — на чистом Redux через middleware можно вообще реализовать интерпретатор любого ЯП.
MobX также увеличивает кривую обучения через Observables
Вы MobX с RxJS не перепутали?
RxJX по мне используют самые отмороженные извращенцы с реактом
Нет, самые отмороженные используют Angular с NgRx.
Можно узнать почему?
Просто взгляни на этот код и потом разматывай клубок чтобы понять че к чему, естественно если этот код написал не ты сам или прошло с момента его написания скажем больше 2х недель. Async/await с 2017 года официально поддерживается, какой нафиг RxJS для реакта, там и так уже давно идеальная эко система в связке с mobx и aync/await
Оригинальный Redux по сравнению с NgRX — образец минимализма и изящества, даже если заморочиться с TypeScript.
Нееет, ну что вы. А проблемы? Об чем писать статьи в духе "грабли redux state роутера или как мы ускорили приложение в 1.5 раза переписав все на классы с поддержкой нового бандлинга вебпак". Кому это нужно то? У нас же все тут пишут дико крутые проекты с миллиардами пользователей где без реакта никак ну вообще никак не обойтись. Я не понимаю как вообще мы без него жили до этого?
Я так понимаю, идея-то заключалась в том, чтобы не писать SPA :-)
Все развалится и будет ад?
Причем тут императивный подход вообще??
Когда абстракции выходят из под контроля и все становится не очевидным, тоже такое себе решение, когда все написано черным по белому и сверху вниз, то и проблем в отладке и поддержке не будет, просто открыл код и все интуитивно понятно без магии скрытой от твоих глаз
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, пока сайты не стали сложными, и с поддержкой говнокода не появились проблемы.
К слову после того как я увидел mobx-state-tree больше ничего другого использовать не хочется. Redux, как мне кажется, используется по инерции, а также из-за большого количества готовых разработчиков под него.
Ниже вам уже ответили, переходите на тайп, не представляю как вообще раньше у меня хоть что-то получалось без дженериков и интерфейсов. Сложно только на стадии создания архитектуры, сама разработка даже для других программистов а команде уже не будет представлять сложности. Опять же совершенно божественный автокомплит и рефакторинг в пару действий сильно отрезвляет и даёт понимание, что читстый ES в 2019 допустим только для прототипов, но не как не для серьезных приложений.
Когда пару лет назад я выбирал, что использовать для типизации, TypeScript попросту оказался мощнее. Опять же без нормальной поддержки в IDE ништяки типизации не так понятны, а поддержка Flow на тот момент была ужасно тормознутая. Как мне кажется, Flow — это полумера. Боитесь чрезмерной строгости — noImplicitAny
и прочие параметры компиляции помогают на первых порах. И, опять же, разработчиков на TS, как мне кажется, больше, чем тех кто умеет правильно писать на Flow. От части это связано с тем, что на NG 2 стали использовать TS.
По поводу преимуществ, которые дает mobx-state-tree
: ненавижу скринкасты, но в свое время я посмотрел один тьюториал, и понял, какой же фигней я раньше страдал.
У mobx-state-tree есть одна киллер-фича — работа с реляционными данными. На уровне снапшотов данных это классический нормализованный стейт, как в Redux. Что позволяет даже использовать Redux Dev Tools. А на уровне бизнес-логики это полноценный объектный граф, как в MobX.
Есть один не приятный момент, из-за которого может падать приложение, хотя могло бы и не упасть и продолжить нормально работать, например ты указал что ждёшь от АПИ объект в котором есть свойство price, и ты ожидаешь number, а от АПИ пришла string и все, кирдык. Хотя по сути без разницы для вывода цены будет ли это string или number. Даже если вам надо сложить 2 + "3.5" у вас все равно получится 5.5 как ожидается, так что и для расчетов это тоже не особо актуально в JS, другое дело когда вам придет не "3.5", а скажем "london", тогда все печально, но от такого вас и mobx-state-tree не спасет, он просто упадет. По мне для типизации в помощь при разработке ловчее использовать flow или typescript(если прям реальная потребность в нем есть на проекте). Но для меня для типизации данных от АПИ нету нужды, элементарно просто открыть network и все там видно, в каком формате что и как приходит, тем более как обычно в АПИ в ходе разработки может все меняется и ни раз, и вместо того что исправлять в одном месте, ты всегда исправляешь минимум в двух
Даже если вам надо сложить 2 + «3.5» у вас все равно получится 5.5 как ожидается
Мы сейчас точно точно говорим о JavaScript? При сложении числа со строкой произойдет конкатенация:
2 + "3.5" === "23.5"
Есть один неприятный момент который был описан в множестве хороших книг по программированию. Момент этот называется так: никогда не доверяй данным приходящим извне, проверяй и валидируй… ну или на крайняк изобрети мегатонны супермодных решений чтобы этого не делать.
Какие у Mobx есть минусы в рамках производительности и сложности развития/поддержки приложения?
Я и более чем на 4х проектах, никаких минусов, сплошные плюсы, реально без сарказма
Redux — это хорошая идея с ужасной реализацией. Хорошая идея — это TEA, которая с успехом используется всеми любителями Хаскеля. И даёт все любимые хаскелистами плюхи — чистоту, простоту композиции и рефакторинг в функциональных системах с развитой системой типов.
Ужасная реализация — это Redux. Который предлагает писать на языке не функциональном, без развитой системы типов, с костыльной дорогой иммутабельностью и кривой функциональной композицией.
Хочешь бойлерплейта и математического доказательства точности своей программы — пиши на Elm.
Во всех остальных случаях — бери MobX и не пытайся засунуть квадрат в круглую дырку.
Хорошая идея — это TEA, которая с успехом используется всеми любителями Хаскеля. И даёт все любимые хаскелистами плюхи — чистоту, простоту композиции и рефакторинг в функциональных системах с развитой системой типов
Чтобы оно давало плюхи, надо dispatch заменить на иммутабельный. А на данный момент это обычная императивщина, без каких-либо плюх. Экшены не композятся, сайд-эффекты вокруг летают и все вот это вот.
Вам просто не надо использовать никакую оптимизацию в этом случае
можно забыть о Pure Components, shouldComponentUpdate и что вы там ещё используете в этих случаях
вы забудете о проблемах с оптимизацией навсегда.
Может я чего недопонимаю?! Дано: большое сложное приложение, на верхнем уровне меняется @observable значение и компонент верхнего уровня render-ится чтобы отразить эти изменения. Всё древо снизу не мемоизировано (нет PureComputed, Memo, shouldComponentUpdate и пр.). React в рамках 1-ой фазы render-а вынужденно пройдёт по всему древу вглубь до самого последнего листа. Будут вызваны все render методы вашего приложения, будет сформировано с нуля всё vDom-древо вашего приложения, и потом оно же реконслировано (без особого на то смысла).
Я где-то что-то напутал? Или ^ просто не считается проблемой в мире MobX?
Любой observer сам по себе мемоизирован. Другое дело, что это достигается либо через memo
, либо через PureComponent
, либо через shouldComponentUpdate
— так что именно с этой стороны ничего нового mobx не предлагает; проставить всем компонентам декоратор observer
ничуть не проще чем проставить им всем базовый класс PureComponent
.
Вот смотрите, наглядный пример, перерендер только того, что надо, а не всего дерева если на верхнем уровне изменился стейт.
Касательно сравнения 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 — это библиотека для решения вполне определенной технической проблемы. Это не архитектура, сам по себе MobX — это даже не контейнер состояния. Вышеуказанные примеры надуманны, и я рекомендую использовать вам, такие подходы как инкапсуляция логики в методах и организация ее в контроллерах и сторах. Или как кто-то сказал на HackerNews
«MobX, уже где-то упоминалось, но не могу не присоединиться. Использование MobX означает, что в архитектуре приложения Вам придется самостоятельно побеспокоится, об использовании таких шаблонов управления потоком данных, как контроллеры/диспетчеры/экшены/супервизоры вместо чего-то предлагаемого по-умолчанию для приложений сложнее списка дел.»
Это отсюда если что.
Ясно, вы один из тех кому что-то сказали и он слепо в это верит, а если ещё и статью кто-то написал так это вообще истина в последней инстанции. Учитесь думать и анализировать своей головой пожалуйста, а не чужими, и опираясь на слова по вашему мнению "авторитета". А ещё было бы круто самому все это попробовать не на уровне hello world и todo и потом уже делать выводы и умозаключения, вместо того чтобы ссылаться на одну из миллионы бесполезных статей и чьих то высказываний которые вводят в заблуждение доверчивых людей, выдавая это за истину и что именно так оно и есть как там написано.
Ясно, вы один из тех кому что-то сказали и он слепо в это верит, а если ещё и статью кто-то написал так это вообще истина в последней инстанции.
Я привел, не статью я рекомендацию автора библиотеки, которую он привел в официальной документации.
А ещё было бы круто самому все это попробовать не на уровне hello world и todo
А вы попробовали? Поделитесь пожалуйста.
ссылаться на одну из миллионы бесполезных статей
Полезна ли MobX? Полезна ли документация к ней? Может ли у полезного проекта быть бесполезная документация? А полезная документация у бесполезного проекта?
Я с лично с 2017 года реализовал больше 4х проектов с mobx и все шикарно и без проблем, плюс с этого же периода в нашей компании все начали вместо redux использовать mobx и радости их нету придела по сей день, т.е в рамках нашей компании этих проектов уже больше 10 сложного и среднего уровня и вообще без нареканий, в некоторых пробовали и mobx-state-tree, но в итоге кроме оверхеда и подводных камней — реальной пользы не принес, то чего от него ждут лучше добиться другими путями если уж так надо. Поэтому опыта а меня дофига в реакте с 2015 года как с redux + его зоопарк, так и с mobx с начала 2017 года.
Из опыта (а я допытывался примерно у 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}} />` не прокатит?
Удивительный комментарий. В первом абзаце идут здравые вещи, а дальше – какой-то набор антипаттернов.
Цукерберг писал на 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. А для сторонников слабой типизации и "магии" языков и фреймворков удобно написать пару строк для решения задачи, а не мучаться с соответствием типов.
Строгая типизация не означает, что для использования фичи нужно всегда совершать дополнительные действия. Она вообще никак не связана с обсуждаемой проблемой.
Я наблюдаю, что в программах на строго типизированных языках есть нефункциональный код связанный с типобезопасностью. Декларации самих типов, объявления типов переменных, параметров, результатов и т. п., формальные (без изменения данных) приведения типов и т. п. Вы считаете, что этот код никак не связан с тем, что языки строго типизированы? Или считаете, что для появления этого кода не совершаются никем никакие дополнительные действия?
Как правило, у этого кода есть некоторая цель, отличная от просто использования фичи. А если такой цели нет — от него точно так же стремятся избавиться.
Очень часто именно для использования. Вернее глобальная цель есть у введения типобезопасности на проекте, но для рядовых разработчиков, которые типы пишут часто этот код именно для использования чего-то, чтобы просто скомпилировалось для начала, а потом ещё и код ревью прошло у того, кто за типобезопасность переживает.
Питонисты, да и вообще фанаты строго типизированных языков с вами не согласятся. :) "Явное лучше неявного" и так далее.
Здесь явное/неявное не при чем. Я говорю о том, что вам либо приходится совершать некие специальные действия, чтобы все заработало (причем смысла в этих действиях никакого нет, в отличии например от типов или какого-то явного описания, вы просто боретесь с фреймворком), либо не приходится. Речь просто о том, что стандартный сценарий использования сломан.
Ну вот например если вам надо повернуть ключ зажигания, чтобы машина завелась (при этом она не заводится от того что вы в машину сели или просто к ней подошли) — это "явное". А когда вам надо для того, чтобы ее завести, приоткрыть багажник под определенным углом и хлопнуть дверцей (одновременно с тем, чтобы ключем покрутить) — вот это сломанный сценарий :)
Очень типобезопасность напоминает :) Когда разработчики библиотеки решают, что я случайно могу передать значение не того типа, которого они ожидают. При этом если мне вообще ничего не надо передавать, приходится делать какой-то null object или передавать undefined
Очень типобезопасность напоминает :)
Никак не напоминает. В случае с типами вам не надо ничего делать — если ф-я ожидает, например, число, и вы суете туда число, то это just works.
А кнопку нажать, чтобы завелась не удобнее будет? А ещё лучше вообще без активных действий на заведение направленных. Сел на водительское сидение, например, и она завелась.
А это уже как раз из разряда неявного поведения. Дело вкуса. Мы обсуждаем другой кейс.
Вот еще пример (как раз близкий к "случайно передать не то") — это история с getDerivedStateFromProps и prevProps. Иметь возможность обратиться к prevProps — это вполне стандартный сценарий использования данной ф-и, он нужен очень часто. Но его не добавили. Не добавили почему? Потому что при первом рендере prevProps = undefined и это могло бы кого-то смутить. Теперь все вынуждены для данного кейза дублировать пропсы в стейт.
Т.е., фича не добавлена просто потому, что кто-то специфический в каком-то специфическом случае теоретически мог бы ее использовать неправильно. И ради того, чтобы этого специфического случая для каких-то специфических людей не возникало — всем приходится для реализации данного кейза писать более сложную логику, в которой допустить ошибку гораздо легче.
Ну это уже не говоря о том что вообще сделали getDerivedStateFromProps статик методом из-за чего ее стало крайне неудобно использовать в принципе. А статик методом она сделана почему? Чтобы нельзя было из метода обратиться к текущим пропсам, все верно :)
Причем был работающий componentWillReceiveProps, который делал свою работу и всех устраивал. Кроме пары людей с квадратно-гнездовым способом мышления :)
К слову, в других фреймворках (всякие ангуляры, вуе, исчезающие свелте и даже прочие, не к ночи будут понмянуты, $mol) таких проблем просто не стоит.
ЕМНИП, они убрали componentWillReceiveProps ради concurrentMode. Просто потому что при асинхронном рендеринге может быть выкинуто несколько "фреймов" компонента и prevProps "отстанут" на несколько циклов рендеринга. Т.е. результат componentWillReceiveProps будет зависеть от того насколько тормозит рендеринг. А это уже дичь какая-то.
Еще в угоду concurentMode они пожертвовали корректностью работы библиотек, основанных на синхронных подписках. Как Redux и MobX ага =)
Хотя в целом, я с вами согласен. Завтраками с concurrentMode они кормят нас уже два года. Со стороны ситуация выглядит абсурдно — фичи еще нет, а люди от нее уже страдают =) И вообще не очевидно, будет ли от этого реальный профит. А не только красивые презентации на конференциях.
А что даёт 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 то что? Ничего, лучше ничего не придумано.
Придумано: https://habr.com/ru/post/317360/
Это не дичь, а более продвинутая система реактивности, чем в MobX.
Чем она более продвинута-то? Тем, что одна и та же функция используется как для чтения значения, так и для записи?..
Двусторонний поток данных.
Абстракция от асинхронности.
Контроль времени жизни значения.
Человекопонятные идентификаторы.
Во вторых атомах поддерживается квантование вычислений.
Не для всех двусторонний поток данных однозначное "лучше". А с человокопонятностью в $mol ві пошутили? Что $ значит у вас я уже не понимаю :)
Не все способны спроектировать простую надёжную и гибкую архитектуру. И что?
Не пошутил.
Это префикс глобалных неймспейсов.
Ну вот односторонний поток данных и является простым. Двусторонний обмен сложнее концептуально.
Вы не помните критики ваших примеров в топиках типа этого? По памяти, люди, разработчики здесь на Хабре, очень часто советуют вам сделать DX при работе с $mol проще и понятнее. Прежде всего в плане читаемости.
А зачем? На него логика какая-то завязана? И они реально глобальные?
Да нет, двусторонний как раз проще. Забиндил одно свойство на другое и всё работает. Без свистоплясок с экшенами, событиями и прочими стримами.
А какие именно там проблемы с читаемостью? И какое она отношение имеет к генерации идентификаторов из имён заданных прикладным программистом?
Реально глобальные. На них сборка завязана.
Это пока не нужно выяснить, а почему вот это свойство имеет неожиданное значение. С однонаправленным "биндингом" мы всегда знаем откуда оно пришло и легко можем определить где оно изменяется в принуципе (особы хардкор динамический языков в расчёт не беру), с двусторонним где угодно может оказаться биндинг и значение присвоено не искомой перемнной а сбинженной, возможно не на первом уровне, а по длинной цепочке.
А чего не сделать было человекочитаемый префикс типа global_?
И везде писать global_mol_mem вместо $mol_mem? Такое себе удовольствие.
Просто mol_mem? А лучше MolMem?
У вас аллергия на баксы или что? Доллар замечательно позволяет отличать локальные имена и глобальные. На всякий случай — имена есть не только у переменных, но и у полей объектов, методов и пр.
PascalCase ни чем не лучше: https://github.com/eigenmethod/mol/issues/312
У меня аллергия на нарушение общепринятых соглашений. С аллегрией на "бакс" я не смог бы писать на PHP скоро как половину жизни.
Так уж сложилось, что общепринятым в экосистеме JS является именнование конструкторов/класов в PascalCase, а переменных, параметров и членов клаасов в camelCase.
И в целом не понимаю необходимости отличать локальные имена от глобальных. Наличие имён $name и name в одном скоупе в любом случае допускает их смешение хоть при написании кода, хоть при чтении. А если такого наличия нет, то префикс избыточен.
Зачем нужно именно такое именование можете почитать тут: https://habr.com/ru/post/456288/
Понятно зачем — чтоб облегчить "понимание" кода программой. Оказывается, это расширенный стиль, которы я называю 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. Спасибо за наводку, извините, что раньше на этот коммент не ответил.
А почему именно Реакт? Что вы из него используете помимо JSX?
Redux и MobX решают одну и ту же проблему/задачу, если не разводить демогогию и цепляться к мелочам. Из моего опыта все кто опробовал MobX в бою больше никогда к Redux не возвращался, конечно же будут индивидумы которые попробовав в бою MobX останутся с Redux'ом, но я таких не знаю. Знаю только тех кто топит за Redux начитавшись непонятных статей и не попробовав при этом MobX на своей шкуре.
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.
Как насчёт того чтобы просто подумать именно своей головой и пощупать и то и другое и вы удивитесь, что действительно и то и другое нужно для одного и того же, философия и подход, вот в чем различие. Изменяешь состояние и твои компоненты которые следят за ним перерендериваются, вот и все предназначение у обоих. Потому хранить состояние без перерендера вы можете как угодно и где угодно, а тут нужна реактивность, т.е. реакция компонентов на изменение состояния в приложении.
Знаю только тех кто топит за Redux начитавшись непонятных статей и не попробовав при этом MobX на своей шкуре.
А я знаю тех, кто топит за React и Angular, не попробовав $mol на своей шкуре.
Бойлерплейта нету
Да, всё верно, вместо каких-то функций, которые изменяют объекты, вы можете использовать классический ООП подход, с классами, их свойствами и методами.
Ок, и зачем мне классический ООП в приложении где пропагандируется функциональный подход, все данные иммутабельны, а классов нет вообще?
И теперь, вместо написания бесконечного бойлерплейта, можно наконец-то сосредоточиться на написании бизнес логики приложения, что не может не радовать.
Это легко абстрагируется. Например, 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, но там тоже есть нюансы.
я не вижу даже что ещё можно улучшить в реакте или в мобе чтобы было ещё приятнее кодитьНапример, можно еще:
1. Сделать уже из реакта фреймворк, а не конструктор фреймворков. Хорошо, когда есть возможность писать с нуля и самому выбирать технологии. Но в реальности из-за запросов заказчика и скорого дедлайна зачастую приходиться брать готовые решения, каждое из которых реализовано по разному и на разных технологиях, и которые надо переделывать под проект. И тут тебя еще «радуют» проблемы популярных решений.
В vue удобно сделано. Можно при установке выбрать, что ставить (vuex, router)
2. Композицию сделать на props, а не на компонентах, чтобы уже перестать смешивать логику и view, и не писать очередные замены миксин.
3. Условия, циклы в jsx добавить.
4. Двунаправленный биндинг добавить. Можно конечно стейт передавать в кастомные input и самостоятельно реализовать биндинг. Но это не вариант.
Уверен, еще много чего найдется улучшить.
Для меня это не улучшения, а наоборот, если вам это надо, то вам надо писать на vue или angular или ещё на чем нибудь.
То какой сейчас react, mobx и ecmascript, более чем достаточно для тех, кто умеет это готовить.
По поводу заказчиков, никто вам не мешает найти нормальную работу, где нету этого булшита тяп ляп и на скорую руку лишь бы работало. Тут вы никогда не станете реальным спецом. Я последнее 3 года от такой работы просто отказываюсь и работаю только на проектах с нуля где не надо чтобы все работало похер как, лишь бы побыстрее закодить. И поэтому пишу всегда в свое удовольствие и закладываю наработанную с годами архитектуру и постоянно ее совершенствую и не знаю никаких бед и проблем. Mobx, react, moment, axios и все, больше ничего не юзаю, кроме редких исключениях типо задачи когда надо например визуальный редактор html прикрутить.
Это хорошо, что у вас все так хорошо сложилось. Я в жизни не видел ни одного разраба, ни одной фирмы, где прям все хорошо. Только если на словах. У всех свои проблемы. Ладно, не об этом речь.
1,3,4 пункты как минимум присутствуют в Vue
А по второму пункту — смешивание логики и view, ну тут уже все зависит только от того, как код напишите, это не реакт ставит вас в какие-то жесткие рамки
Полноценный фронтенд фреймворк для меня, это когда можно из доступных из коробки фич сделать приложение с клиент-серверным взаимодействием, валидацией, роутингом, стором без явной необходимости скачивать плагины, делающие эти фичи гораздо лучше фреймворка.
Что касается циклов и условий в реализации JSX для Vue, я не знаю, есть ли они там. С Vue я мало знаком.
Ну и к тому же в Vue тоже свои недостатки есть. Не думаю, что один из них (react, vue) настолько лучше другого, что при знании одного, стоит переходить на другой.
По второму пункту.
Реакт изначально делался со смешиванием логики и view. Разделить то можно, до тех пор, пока нескольким компонентам не понадобится общая логика. Решения вроде миксин и HOC по вынесению общей логики вне компонентов не очень себя показали. Решения аналогичного директивам попросту нет. Тут все-таки реакт ставит в жесткие рамки. В общем, реакт гибкий, но мог бы быть еще гибче.
Да, появились хуки. Но неизвестно, куда они заведут. Через пару лет видно будет.
Mobx, react, moment, axios и
… приложение уже весит 125 кб со всеми способами сжатия.
Для сравнения: весь mol.js.org — 85кб даже без минификации.
Делай генерацию html на бэке и забудь о JS на клиенте, ну или прям по минимому, так у тебя вообще весь будет, это вес твоей странички
Если это landing page, то есть смысл сделать так, что бы первая загрузка была максимально быстрая, если у тебя это именно приложение, то вообще пофиг, это последнее на что стоит смотреть, тем более если уже так хочется, то можно сделать SSR и пользователь увидеть все при первой загрузки так же быстро и сразу, а интерактивность начнется уже когда (очень очень быстро) загрузится и положится в кэш твой бандл
И зачем мне ваши костыли? У меня нет проблем с размером приложения.
Если в двух словах, то 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>
Незачем на редаксе писать ТАК, есть куча хелперов.
Одна из проблем мобыкса, в контексте сравнения, это то что внутренняя модель приложения описывается внутри view слоя! С редаксом проще отделить model от view.
P.S. github.com/artalar/flaxom#why-not-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?
С фига ли shouldComponentUpdate вернет false если он в React.Component по умолчанию возвращает true?
Смотрите с 16 по 23 строку
Это observer
Я вот про это: https://codepen.io/fen1kz-the-sasster/pen/ewarap
Меняем тайтл — reallybiglist перерендеривается.
идеальная работа mobx перендер только того, что поменялось.
Не, дичь набросали вы, сделав RerenderTest observer'ом.
Со слов автора, одно из преимуществ modx перед redux в том, что можно забыть про React.PureComponent.
А теперь оказывается что забыть можно только потому, что компоненты надо оборачивать в observer. И как бонус выясняется что modx манкейпатчит shouldComponentUpdate у компонента снизу (что за дичь?) и "It is not allowed to use shouldComponentUpdate in observer based components."
Если мы полностью владеем кодом всех компонентов проекта, то проблем нет. А вот если используем какой-то визуальный фреймворк, то тут уже интереснее. У нас был затык с third-party TreeView, который рекурсивно рендерит сам себя для вложенных элементов дерева. Решений два:
- Как-то пропатчить TreeView, чтобы он стал обсервером. С mobx-react 5 для этого достаточно вызвать observer() на TreeView, т.к. по счастливому стечению обстоятельств это был классовый а не функциональный компонент. Но это блин махровый манки-патчинг в самом худшем его проявлении.
- Вызвать mobx.toJS() на корневом узле данных дерева, как советовали в одном issue от mobx. Это сразу подпишет рендеринг всего дерева на любое изменение данных. Ну и здравствуй пересоздание развесистых объектных графов на каждый чих. И прощай производительность.
В защиту mobx можно сказать что с Redux, как превратить рекурсивный third-party компонент в connected, тоже непонятно.
Но это блин махровый манки-патчинг в самом худшем его проявлении.
В чем конкретно он доставляет проблемы именно вам или вашему проекту?
Второй вариант не просто так советовали. Если TreeView сделан для отображения иммутабельных данных — значит, ему надо передавать иммутабельные данные.
То, что вам удалось отманкипатчить TreeView — это хорошо, но не гарантировано. И эта негарантированость — проблема не mobx, а TreeView.
Для производительности же, надо связываться с авторами компонента, и требовать с них версию для мутабельных данных.
Да это понятно. По хорошему, TreeView должен иметь какой-нибудь опциональный prop renterTreeNode
, в который мы могли бы запихнуть хоть observer(TreeView)
хоть connect(...)(TreeView)
.
Но тогда непонятно, что делать самим авторам UI-фреймворков под реакт:
- Поставлять отдельные версии компонентов под иммутабельные или реактивные данные? И если под иммутабельные еще можно, поскольку это все еще plain-объекты, то под реактивные — это зависимость от mobx (или чего там еще).
- Делать все промежуточные компоненты конфигурируемыми (те, что пользователи фреймворка не используют напрямую, вроде TreeNode для TreeView)? Это уже интереснее, но попахивает каким-то внедрением зависимостей.
А вот о стандартном механизме DI должны были подумать авторы самого React. Сейчас есть много реализаций, но все они — пляски с бубном или как минимум неизвестны широкой публике. И тут у UI-фреймворка опять дилемма — вставлять зависимость на что-то маргинальное? Или ваять свою систему конфигурации?
Это уже кривизна самого Реакта, которому приходится ререндерить весь шаблон, чтобы поменять пропсы у одного элемента в нём. Тут никакой стейт менеджмент не спасёт.
В $mol же, разумеется, такой проблемы нет, но кого это волнует.
Приведу другой пример для объяснения. Использование key-prop при генерации html в цикле по значениям массива — это жуткий костыль. MobX может дать информацию, как именно изменился массив и из этой информации и декларативного шаблона однозначно понятно, как менять DOM — добавить/удалить куски или поменять что-то местами.
И получится KnockoutJS или Vue 1.0. Кстати, в Vue 2.0 они зачем-то же перешли на JSX.
JSX лишь синтаксис по сути.
Только этот синтаксис содержит как подмножество весь js. В результате чего стат. анализ в общем случае становится алгоритмически неразрешимой задачей.
Прошу прощения. Конечно же я имел в виду Virtual DOM в Vue 2
5 причин, почему вы должны забыть о Redux в приложениях на React
4 аргумента в пользу того, чтобы в следующем проекте вы использовали MobX вместо Redux.
Никаких "отталкивайтесь от задач" или "mobx лучше подходит для таких-то проектов" — это навязывание.
Если честно он лучше во всем, любой кто применял его в бою согласиться
В чем прикол брать redux чтобы подключить ещё 10 библиотек которые упростят работу с ним?
Те которые хочешь? Я уверен что вы руководствуетесь тем, о чем упоминается в статьях и написано в списке требований в вакансиях. А не тем, что вы хотите на самом деле. Просто альтернативы лень попробовать и взвесить именно своей головой плюсы и минусы и сделать выбор.
И как раз поэтому каждый проект на Redux, к которому ты подключился не со старта разработки — это целый комбайн спорных/чьих-то чужих решений. Кто-то обязательно натащит туда модных на данный момент библиотек для упрощения работы с ним. Потому что работать с голым редаксом — боль.
Я бы согласился с теми людьми выше в комментариях, которые советуют больше думать своей головой. Подключать по каждому поводу библиотеку оказывается часто сложнее, потому что есть какая-никакая кривая обучения, привыкание.
У нас был JS, мы улучшили его с помощью React, который улучшили с помощью Redux, который пытаемся улучшить с помощью множества сторонних библиотек. По-моему, где-то надо остановиться и просто писать код
Нормальным было бы затягивать в апстрим best practices, опробованные коммъюнити в библиотеках. Как это делают в стандарте языка и Babel-плагинах. А сейчас мы имеем:
Хотите иммутабельного состояния — ну нате вам useReducer()
. Что, Context API не умеет в селекторы? Ну так оно не для управления стейтом, а для тем всяких там. Механизм подписок не дружит с concurrent mode? Ну извиняйте. Короче, не мешайте нам делать красивые доклады на конференциях, а валите в свой Redux / MobX.
Просто непонятно, куда в итоге React развивается. Я с трудом представляю себе достаточно сложный SPA, написанный на голом реакте без state management библиотек. И что же они с этим делают?
Предоставляют более удобные примитивы для облегчения разработки state контейнеров? Нет. Учли наработки community и делают полноценный фреймворк? Нет.
Вместо этого они мутят какую-то свою уличную магию с хуками. А разрабы state контейнеров отплясывают вокруг этого с бубном, чтобы все оно хоть как-то работало.
Да, с хуками стало лучше. Вроде того, что если раньше на голом реакте боль начиналась с 10K строк кода, а теперь только с 20K. Но на 100K все равно нужен state management.
Нет. Учли наработки community и делают полноценный фреймворк? Нет.
https://reactjs.org/docs/getting-started.html:
React is a JavaScript library
Реакт это не фреймворк и никогда им не станет. И это его преимущество, да. Хотите фреймворков — попробуйте что-нибудь другое?
Поймите, Redux тоже брали из рассчета, что он решит все насущные проблемы, однако потом вдруг выяснилось, что в рендер компонентов каждый раз прилетает новый объект (даже если данные не изменились), кто-то не уследил за код-ревью и в рендер упал Array.map и был передан в компонент ниже и так далее, а может код ревью не было вовсе?
Банальные кейсы, на вскидку, которые решаются на редаксе (или ngrx-store в ангуляр в связке с какими-нибудь эпиками/эффектами) по щелчку пальцев и которые нужно упороться делать на мобх и ему подобных:
- Анду/реду;
- Оффлайн мод;
- Кансел/ретрай кейсы (нестабильная сеть, релогин, прочией кейсы);
- Лоадинг стейты;
- Канкарент модификации состояний (реализация полноценной асинхронной работы с приложением с участием нескольких пользователей);
- Централизованная обработка ошибок;
- Бонус: отсутствие рейс кондишенов.
Все эти кейсы условно-бесплатные со однонаправленным стейт-менеджером.
Анду/реду
Я бы посмотрел, как вы это реализуете на редуксе. Вы хоть раз реализовывали анду-реду?
Оффлайн мод
Никак не зависит от стейт менеджмента.
Кансел/ретрай кейсы (нестабильная сеть, релогин, прочией кейсы)
Никак не зависит от стейт менеджмента.
Лоадинг стейты
В МобХ из коробки.
Канкарент модификации состояний (реализация полноценной асинхронной работы с приложением с участием нескольких пользователей)
Никак не зависит от стейт менеджмента.
Централизованная обработка ошибок
О чём речь?
отсутствие рейс кондишенов
Любая асинхронноть даст вам рейс-кондишены независимо от стейт менеджмента.
Там всё усложнено не просто так. При реализации анду-реду важно указывать какие экшены влияют на историю (только прямо или косвенно индуцируемые действиями пользователя для внесения изменений). И какие именно состояния трекаются. Нельзя просто так брать и трекать всё подряд по любому чиху.
Ну а в вашей реализации получается ручное вынесение трекаемых свойств в отдельную ветку дерева. Рулить двумя параллельными деревьями (трекаемое и не трекаемое) — такое себе удовольствие.
Нажимаешь undo и получаешь "Saving...", а чтобы увидеть данные нужно ещё раз нажать?
В МобХ из коробки.
Вот это особо повеселило. Это расстановка true/false флажков isLoading, то самое решение из коробки?
Да нет, прекрасно понял. От стейт менеджмента количество приседаний мало зависит. Если не учитывать, что для редукса объём бойлерплейта в несколько раз больше.
Можно подумать в Redux не флажки isLoading будут.
Можно подумать в Redux не флажки isLoading будут.
Как там в мире ту-ду листов и доставок пицц поживается?)
Писаемся в песочнице. По существу что-нибудь скажете?
Обвязка в виде класса для запросов к АПИ творит любые чудеса, хотите счётчик активных запросов будет писать в mobx, хотите будет автоматически добавлять/изменять isLoding у стора или у компонента, смотря что засуните в аргумент, и т.д и т.п. творчеству нет придела
Через MobX вы можете сделать то же самое, только без этого "имплеситный кусок пейлоада в экшене, в котором нужно указывать ассоциированный экшен по имени", что бы это ни значило.
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;
}
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);
}
}
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 или каких-нибудь ещё бойлерплейтных флажков для данных, которые асинхронно загружаются.
реализация полноценной асинхронной работы с приложением с участием нескольких пользователей
Этот кейс я что-то не понимаю. У нас же с приложением на фронте один пользователь работет?
Во-первых надо думать от задачи, а не от того как выглядит код. А также учитывать какие самые важные базовые принципе заложены и продвигаются библиотекой. В случае 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?
Доброго дня.
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 и является источником нужных для рендера данных, из него данные могут сортироваться, фильтроваться и использоваться в различных представлениях.
Чего-то вроде и "и сами данные сета могут изменяться" там нет.
Чего-то вроде и "и сами данные сета могут изменяться" там нет.
https://habr.com/ru/post/459706/#comment_20387548
Свойства a
и b
— observable.
Он просто троль, ему без разницы че ему пишут
В чем вообще проблема выкинуть дубликаты?
В том как всё это делать эффективно.
Есть такая структура данных HashSet называется.
Пробежался сейчас по исходникам 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 и на экране отображается пара десяткой рядов, а в памяти лежит несколько тысяч.
Просто абсолютные значения в миллисекундах, без сравнения. Потому что сравнивая на глаз у вас явное отторжение к мобх, поэтому будете судить предвзято. Чтобы это исключить нужны просто абсолютные значения в цифрах, типо запушить 5000 итемов в стор мобх заняло столько то. Это будет судить именно о производительности мобх, а не о какой то обстрактой в которой N составляющих
При том, что большинство действий довольно типовые:
— запросить данные с сервера
— немного преобразовать
— присвоить в стор, чтобы отобразить
В MobX решается одним фетчем, одной чистой функцией декоратором и одним нативным присваиванием. В redux, тоже есть fetch (правда с довеском из saga, thunk и т.п.), тоже чистая функция декоратор, только вместо присваивания какая-то дичь с иммутабельностью, не к месту применённому паттерну «команда» и другого барахла, которое я не заказывал. Говорят легко тестировать, только тесты получались однотипные, проверить что action-creator создаёт правильный объект (ни разу не сломались), или что switch правильно отрабатывает и возвращает правильный ответ (только чистые функции и без redux прекрасно тестируются, в MobX тоже).
По производительности, да, можно написать свой combimeReducers, можно заменить switch на хеш или сравнение строк на числовые константы или лучше битовые маски, можно генерить весь бойлерплейт на каком-нибудь yeoman, можно много чего улучшить, только это будет уже не redux, а самописное решение на основе проблемной технологии. Новый человек, когда придёт, будет какое-то время изучать особенности вашего кастомного оптимизированного redux. Хорошая библиотека должна иметь простой интерфейс, и скрывать сложное и умное решение, которое долго писать самому (это как раз MobX), а плохая библиотека имеет сложный интерфейс, с кучей условий и органичений, и простую внутренность, которую легко и самому написать (узнаёте?).
Субъективный нетехнический плюс MobX — он хорошо ложится на классические практики DDD в частности и ООП в целом. На бэкенде они применяются чаще ФП и в случае MobX гораздо проще добиться понимания между фронт и бэк частями команды, да и бизнесу проще обычно оперировать сущностями с поведением чем иммутабельным стейтом с редьюсерами. А фуллстеку не нужно десятки раз за день переключаться между парадигмами.
Два основных "формата" данных, которые надо обрабатывать в обічніх бизнес веб-приложениях: формы UI и записи в РСУБД. Оба предполагают некую контекстную собранность. Оба предполагают мутабельность. ООП служит хорошим связующим звеном между ними и при этом хорошо ложится на схемы бизнес-процессов.
А у оптимальности много аспектов, один из них — скорость реагирования на новые и изменившиеся бизнес-требования. Если бизнес, бэк и фронт говорят на одном языке, то, как минимум, не нужно терять время на перевод с языка сущностей, действий и событий на язык иммутабельных структур, редьюсоров и селекторов с одной стороны, и язык реляционной алгебры с другой. А для бизнеса пресловутое "тайм ту маркет" часто важнейшая метрика для команды разработки.
Мы с коллегами тоже задумались над этой проблемой, наше решешие — библиотека react-hoox, которая родилась нашими стараниями.
Бойлерплейт отсутствует, один маленький хук и любой объект становится реактивным.
Через какое-то время мы напишем полноценную статью на хабре про нее. А пока в тестовом режиме собираем фитбек и пробуем в продакшене
Нашел на гитхабе, использовать я это конечно не буду)
Подробный мануал есть на npmjs https://www.npmjs.com/package/react-hoox
На редаксе его используют, когда не хотят засорять глобальній стор локальными данными. Две альтернативы. Редакс же не позволяет создавать локальные сторы? Или позволяет, но это очень геморрно? С MobX есть третья простая альтернатива: просто observable объект вне глобального стора. Хоть вне компонента, хоть внутри его как полная замена стейта
MobX (без дополнительных расширений), насколько я понимаю, решает одну проблему: точечное (и поэтому эффективное) обновление UI без перерендера всего дерева.
Redux решает проблему структурирования бизнес-данных приложения и управления состоянием, но в части обновления UI он работает неэффективно, полагаясь на эффективность рендера Реакта (зря) и некоторые простые оптимизации (тоже зачастую нерабочие).
Идеальным было бы (ну, по крайней мере, для меня) совмещение этих двух подходов. Возможно, именно этим и занимается mobx-state-tree.
В плане управления и структурирования состояния для меня Redux вполне норм, если обмазаться хелпер-либами (но это изнанка простоты редакса). В плане оптимизации рендеринга Редакс ужасен.
Было бы круто их совместить, но без заморочек MobX типа описания классов для всего.
но без заморочек MobX типа описания классов для всего
А это как раз наиболее полезная часть, потому что позволяет совместить в одном месте объявление типа (которое нужно чтобы не запутаться самому) и реализацию хранилища (ради которой все и затевается).
Если использовать тайпскрипт, то эти описания, зачастую, — двойная работа, тк все-равно нужно описывать результат ответа АПИ. И совместить их не всегда возможно.
Если вы решили хранить ответ API целиком — то вам вообще не требуются классы, простого observable.ref
более чем достаточно.
Если же предполагается какая-то обработка — без отдельного типа не обойтись.
На самом деле, никто не мешает для MobX завести не несколько хранилищ, а одно, как в Redux. Пару проектов делал именно в таком ключе. Один на MobX, другой по сложнее на mobx-state-tree.
Правда, бизнес логику нужно будет вывести в отдельный слой сервисов. А стор тогда получится вырожденным. Просто хранилище реактивных данных. И не нужно никаких отдельных классов. Да, в таком варианте мы несколько жертвуем эффективностью. В случае классов мы можем выборочно навешивать @observable. А тут реактивным будет все. Но в Vue же как то живут с этим и не жалуются.
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) на одни реактивные данные, изменять другие реактивные данные. Но за такое надо бить по рукам.
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 бессмысленных итераций в течении которых всё может измениться до неузнаваемости и ищи потом концы. Для примера, как сейчас выглядит исключение в $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 секунд и похоже попали в бесконечный цыкл, вот вам стектрейс, разбирайтесь сами, где накосячили".
2) Он говорит где именно он заметил косяк
Если одна итерация занимает 100мс, то 100 итераций занимают 10с.
Обратите внимание на два стектрейса, помогающие понять как так получилось, что образовался цикл. МобХ же судя по коду выводит лишь реакцию, в которой обнаружил проблему. Но причина проблемы может быть глубже.
Если одна итерация занимает 100мс, то 100 итераций занимают 10с.
Если у вас итерация занимает 100мс, то это значит, что приложение тормозит на уровне невозможности пользоваться им.
Вот вы не уточнили итерация чего и в каком приложении, а уже поставили диагноз. Не надо так. Много ли вы знаете приложений инициализация которых занимает меньше 100мс?
Много ли вы знаете приложений, инициализация которых выполняется в цикле?
Прочитайте, пожалуйста, ветку обсуждения с начала: https://habr.com/ru/post/459706/#comment_20407997
Не надо так. Много ли вы знаете приложений инициализация которых занимает меньше 100мс?
"Итерация" — это то, что регулярно повторяется, так что инициализация точно не подходит. В данном случае из контекста совершенно ясно, что это вычисления на каждом рендере.
Перечитайте предыдущие посты, вы потеряли контекст обсуждения.
Я говорю даже не о циклах в обработке реакций. Это уже за гранью добра и зла.
Даже если программа написана корректно: изменение одного 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
- частая необходимость эмуляции графов чем-то типа реляционных связей или огромная избыточность, причм ещё заставляющая использовать сравнение по значению. Хорошо если не глубокое.
- Да, отсутствуют описания классов модели. Я использую тайпскрипт, поэтому такой проблемы нет.
- Можно, но нужно их копировать при изменении, либо использовать иммутабельные аналоги
- Да, есть такое
- Ну опишите вы типами или интерфейсами шейп, аналогичный свойствам моего класса. Ну селекторы будут возвращать этот тип. Но данные только часть модели. Как понять, что диспатч какого-то редьюсера изменит нужный мне "класс"? Особенно если, как у многих принято, редьюсеры в отдельной паппочки, селекторы в другой, а типы, наверное, в третьей (ни разу редакс+тайпскрипт не видел).
Необходимость в денормализации по идее есть и в мобх, иначе будут высокие затраты на обновление больших поддеревьев
Можете пояснить, какие затраты имеете в виду? Модель данных в MobX – это мутабельный объектный граф. Он уже денормализован "из коробки".
Если у нас граф объектов с глубокой вложенностью, обновление большой ветки графа должно привести к пересчету всех его зависимостей (в том числе и UI). Потенциально это может быть медленно: отписаться от старых данных, обновить данные, пересчитать зависимости.
Поддерживает ли MobX инфо о том, какие зависимости у детей той или иной ветки графа?
Что будет, если полностью перезаписать значение prop1?
codesandbox.io/s/pedantic-bogdan-l8c4e
На самом деле здорово. Как они, интересно, отслеживают изменения по всему дереву?
Поигрался немного с вашим примером https://codesandbox.io/s/wonderful-pond-gtcmx
Действительно, рендерит только то, что нужно.
Нормальная тема, с точки зрения производительности рендеринга вообще супер, но мне нравится подход редакса к организации и структурированию приложения.
Но, получается, если 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()
единственный вариант для такого кейса.
Насколько я понимаю, MobX оборачивает каждый объект в графе в прокси. Хорошо подходит для случаев, когда данные загружаются однажды и редко меняются (ну в принципе любой CRUD и вообще большинство вариантов использования). Для статических частей стора (типа статических гридов) достаточно сравнивать объекты по ссылкам (как в классическом редаксе).
Для часто перезаписывающихся данных (типа отображения обновляемых данных с сервера) это подходит хуже, т.к. нужно опять же проксировать всю иерархию, и это все-равно приведет к перерендеру всего поддерева UI.
Вот даже кто-то написал эту функцию github.com/jeremyhewett/mutate-object
MobX отлично справляется с часто меняющимися данными без перерендеринга всего поддерева. Главное — не использовать immutable подход как в редаксе, а именно менять данные. Пришла новая версия объекта User с id = 1 — не создавать новый объект, а пропатчить существующий. Отрендерятся только те компоненты, которые зависят от реально изменившихся свойств (если деструктуризировать в пропсы нормально, соблюдая правило передавать в observer компоненты через свойства observable объекты, настолько малые насколько возможно, но не доводя до примитивов, то есть предпочиать <UserAvatar user={user}/>
а не <UserAvatar url={user.avatarUrl} />
Присоединяюсь к MaZaAa. MobX вообще пофиг на вложенность. Можно использовать даже не дерево, а циклическую структуру данных.
Все подписки точечные (только на те поля, которые были затронуты). Все изменения данных тоже точечные. В отличие от Redux, при изменении вложенного поля, здесь не копируются объекты по пути к этому полю. Поэтому и компоненты, подписанные на вышестоящие объекты, не перерендерятся.
Есть у нас две сущности Contract со свойством customer типа Person. В РСУБД или редаксе это, скорее всего, будет, столбец/поле customer_id в таблице/массиве/хэше contract и отдельная таблица/хэш person, чтобы избежать дублирования. Но в результатх выборки этих договоров определить что это один клиент можно только по ид. В графовой/объектной СУБД, чистом JS или MobX это скорее всего будет свойство customer, ссылающееся на один объект Customer. И что это один клиент можно определить сравнениями как ид, так и ссылками. Ссылками быстрее и естественней.
И откуда вы возьмете ссылку на уже загруженный объект Customer без нормализации?
Большинство проектов и библиотек работают с Redux — проще интергрироваться при необходимости.
Только если эти библиотеки написаны для Редакс
Redux создает каждый раз новый экземпляр stor'a — поэтому можно отследить в reduxDevTools историю изменений stor'а.
Можете подсказать зачем это надо?
Redux — функциональщина которая понятно как работает, mobx — какая то магия.
Тогда проще писать на ванили.
Redux — функциональщина
Нет.
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() компонента абсолютно бессмысленно (и даже вредно).
Подробнее можно прочитать в документации.
1. С редаксом, при клике вызывается экшен и страница перерендеривается, хотя на ней нечему обновляться: codesandbox.io/s/reactredux-70hm1
2. С мобиксом, при клике компонент не перерендеривается, потому что на странице нечему меняться: codesandbox.io/s/mobxreact-s7db5
В идеале конечно такого не должно происходить, я должен был убрать это свойство из mapStateToProps, но в данном случае MobX мне простит ошибку, а Redux — нет.
А, ну в таком случае, конечно, будет перерисовка — компонент через connect()
подписан на обновление state.value
, хотя он ему не нужен.
Но это совсем другая история — PureComponent
тут тоже никак не поможет и он, опять же, в данном примере совершенно лишний.
В целом, пример на MobX любопытный, спасибо (честно скажу, не смог заставить себя вникнуть в примеры в статье, а в песочнице оно куда нагляднее).
Некоторые соображения:
- На практике такой сценарий достаточно редкий. Подписываются на store обычно т. н. controller-view (а их ограниченное количество), которые полученные из store данные спускают вниз дочерним pure-компонентам. Соответственно, controller-view сам никакого UI не создаёт и его перерисовка максимально дешёвая.
- Ошибки подобного класса весьма хорошо выявляются статическим анализом.
- Если закрыть глаза на пп. 1 и 2, то остаётся reconciliation. Да, это недешёвая операция, но, насколько я понимаю, "реактивная магия" MobX тоже отнюдь не бесплатна. Пожалуй, было интересно увидеть честное real-world сравнение производительности подходов Redux и MobX, но вряд ли мы такое когда-нибудь увидим. :)
5 причин, почему вы должны забыть о Redux в приложениях на React