Comments 53
Как обычно, хочется услышать несколько слов, почему MobX а не Redux. Для непосвященных.
Вообще туториалы типа "как настроить с нуля такую-то связку" не предполагают раскрытия вопроса "почему эту связку, а не конкурирующую".
Так я же предлагаю развернуть дискуссию не в самой статье, а в комментариях.
Какой смысл в таких статьях, если они быстро устаревают? Куда проще почитать офф. доки нужной либы
Лучше бы раскрыли, какой смысл в Mobx
Мне вот очень нравится Mobx, но смущает его магия. Свободного времени сложно найти на то, что бы разобраться в его магии и разобраться, где стоит использовать мобикс, а где нет. Было бы приятно почитать статью на эту тему
MobX, RxJS и Redux являютя, по сути, различными идеями реализации реактивного программирования (pull, push и что-то там особое), каждое из которых дает свое преимущество и недостатки (а еще их часто между собой комбинируют).
MobX, в отличие от redux, больше подходит для построения MVVM архитектуры приложения и хорошо структуризированного ООП, когда redux стремится в функциональщину и нормальным сторам. За счет того, что он (mobx) скрывает в себе много кода по обновлению компонентов, инкапсулирует логику обновления компонентов в себе (в хорошем mobx приложении вы ни разу не должны прописывать shouldComponentUpdate), при этом перерисовывает только непосредственные зависимости в отличие от перерисовки всего View в редуксе, ускоряет и облегчает разработку, требует меньше оверхеда в виде постоянного создавания всевозможных action и reducer, а так же увеличивает отзывчивость страницы. В теории.
Я бы даже сказал так: в очень оторванной от действительности теории, покуда на практике красивый код из todo-mvc примера превращается в достаточно запутанный VM слой с геморроем при lazy-загрузке, постоянными лишними перерисовками, которые устанешь вычищать, а так же хитрыми костылями там, где он по какой-то причине предпочел работать не так, как тебе нужно. Зато на нем действительно получается изящная MVVM архитектура.
постоянными лишними перерисовкамиСначала тоже наткнулся на проблему с лишними перерисовками, но потом прочитал, что она решается использованием транзакций с помощью action и runInAction — https://mobx.js.org/refguide/transaction.html. Не знаю, может быть вы их использовали и у вас все-равно были лишние перерисовки по другой причине.
Насчет архитектуры приложений, использующих MobX.
Из того, что я прочитал/попробовал, не вижу проблем делать архитектуру с MobX как душе угодно. Главное, что можно сделать любые объекты наблюдаемыми и, использовав соответствующие поля этих объектов в autorun, action, computed или render, автоматически подписаться на их изменения.
Можно сделать один большой стор, можно несколько маленьких. Можно однонаправленный поток данных, можно MVVM, можно заодно и стейты компонентов заменить на observable объекты. Также нет необходимости писать методы в сторах. Можно оставить только данные, ну и вычисляемые значения. Хотя @computed, как и action можно вынести в любое место программы. Нет необходимости писать их в том же наблюдаемом объекте.
Недавно начал новый проект. Сейчас решил заменить в нем нынешнюю библиотеку управления состоянием на MobX. В моем случае не вижу необходимости менять существующую архитектуру.
Для меня лично MobX стойко ассоциируется с Knockout и всеми его болячками.
Плюс не нравится описывать структуру данных модели и загрузку ее данными сервера, как было в Нокауте. Хотя, может быть, в МобХ такой проблемы и нет.
Если да, то как?
Если нет, то есть ли аналоги, которые можно?
Если у вас бекенд отдает обычный JSON, то какая вам разница, что с ним происходит во фронтенде?
Что с MobX, что с Redux что с ванильным JS все одинаково. Загрузили данные, что-то обновили, отослали обратно.
Вам надо искать не какие-то "обертки для реактивности" — а просто парсеры нужного вам формата. Ну или перейти на JSON, это не так сложно.
Мне нужна именно магия для реактивности.
Если она будет работать через JSON, прилетающий по весокету — будет отлично.
Кажется, вы все еще не понимаете...
Данные не несут в себе никакого "запаха". Считанные из файла, прилетевшие через AJAX, прилетевние через веб-сокет, введенные пользователем — все они остаются данными. Библиотека работает с ними одинаково.
Библиотека умеет хранить данные, менять данные, строить графы зависимостей и уведомлять об изменении данных.
У ваших объектов есть методы-экшены, которые изменяют их состояние, вызывая нужные вам реакции, как и ререндеринг React UI (через mobx-react), так и вызовы сервера для сохранения (задаете явно) — это магия MobX: после экшена уведомить об изменении всех потребителей объекта, о которых вызывающий экшен не знает в общем случае. Эти экшены дергаете откуда хотите по любым событиям хоть UI, хоть ответу http, хоть сообщению WS, хоть по таймеру, любым доступным в JS способом.
Обертки, готовые к чему?
Все вышеупомянутые библиотеки не делают никаких ограничений на общение с сервером. В этом и их достоинство в общем-то.
Можно и сокетами, если вам так нужно. Вот как это будет на MobX:
class LogStore {
@observable logs = [];
constructor({socket}) {
socket.on('newLog', this.newLog.bind(this));
}
@action newLog(newLog) {
this.logs.unshift(new LogEntry({...newLog }));
}
Взято отсюда:
https://github.com/jeffijoe/logpipe-server/blob/master/src/frontend/app/stores/LogStore.js#L35
Вот есть куча @observable. Некоторые из них я хочу синхронизировать с сервером. Чтобы при изменении на клиенте они тут же отсылались на сервер. И в обратную сторону — при изменении на сервере обновлялся клиент.
В вышеупомянутом LogStore это делается руками. Вот есть ли какая-то библиотека, которая автоматизировала бы процесс синхронизации стейтов между сервером и клиентом?
Я для этих целей использую Apollo Client (и Server, но это нюанс), но с MobX он не интегрирован от слова "никак", всё ручками.
В общем виде в библиотеках магического сохранения данных на сервер нет, потому что это сильно завязано на конкретный способ хранения данных и бизнес-логику. В общем виде это не оформить.
Как вариант, есть Apollo. Это что-то похожее на Meteor, только поверх React и GraphQL. Но, кажется, это не то, что вам нужно.
Если не замахиваться на универсальную "магическую" реализацию, то вполне себе нормально их клиент интегрируется с MobX, включая подписки на обновления по веб-сокетам
А за универсальность… Ведь у Метеора получилось довольно универсально, через синхронизацию Монги
Не копал Метеор, но "по слухам" это фулл-стэк решение от фронта до монги. Связка же MobX+React от бэкенда никак не зависит, а Apollo требует реализации GraphQL, а они есть не менее чем на десятке языков.
По универсальному MobX+Apollo есть наметки на https://github.com/apollographql/apollo-client/issues/503
с одной стороны, у нас есть метеор, который в целом работает.
с другой стороны «небольшой стек» — Java-GraphQL-Apollo-MobX-React который ещё нужно суметь запустить. Ведь в каждом компоненте из списка есть какой-то нюанс.
мне кажется, что порог входа во втором случае на порядок выше, чем в первом. И не понятно почему так. Почему нет более простого решения. Может быть оно никому и не нужно?
На сколько я понял, работая с метеором, в нём нет возможности напрямую сделать реактивную переменную/поле — реактивность завязана на Монгу.
В длинном стеке же можно сделать реактивную переменную, но ценой довольно слоистой архитектуры. Возможно, из-за этого отличия и получается такая разница в сложности…
{store:"user", method:"edit", fields:"{id:1, name:"firstname",...}"}
обработчик на клиенте принимает сообщение, парсит что/как нужно изменить и дергает необходимый метод, например:
userStore.editUser(fields);
@observable users = [];
...
editUser(fields){
let user = this.users.filter(user=>{
return user.id==fields.id;
});
user.set(fields);
}
так как у вас будут observer компоненты, которые будут наблюдать за юзерами, то они автоматически перересуются.
Мне почему-то казалось, что есть готовая библиотека с магией, которая сделает за меня вот эту работу:
обработчик на клиенте принимает сообщение, парсит что/как нужно изменить и дергает необходимый метод,
а так же в другую сторону.
Обновилось, например, на каком-то из клиентов firstname, а об этом сразу узнал сервер и потом все клиенты. сами, без дополнительных телодвижений. А потом эти изменения ещё и отобразились в шаблонах. Сами.
Походу, такая магия есть только у Метеора. У остальных — только реактивные переменные.
Не уверен, но вроде то, что вам нужно: gritzko/swarm, josephg/ShareJS.
такая магия есть только у Метеора. У остальных — только реактивные переменные.
Внезапно такая магия в Метеоре называется ReactiveVar :)
Без разницы с чем использовать. Связка mobx+react (как и redux+react, та и вообще любой нормальный фронтенд не делает предположений о том, что на бэкенде). Более того, mobx+react и redux+react вообще с бэкендом не взаимодействуют, они хранят данные в переменных js, изменяют их и отображают изменения. Откуда данные берутся и куда отдаются вне их зоні ответственности.
А если мне хочется реактивности, подобной Метеору, что из готового взять в дополнении к mobx+react?
Чтобы при изменении данных на сервере все клиенты видели их сразу.
Как я понимаю, нужна какая-то обёртка над вебсокетами. Что со стороны фронтенда, что со стороны бэкенда.
MobX выглядит намного "проще" в использование в отличие от redux где необходимо писать больше вспомогательного кода. Но вопрос в том что выбираем, кажущуюся простоту в написание или отсутствие магии в коде с возможностью протестировать почти 100% функционала. Да, не подготовленного человека redux испугает всеми своими actions/reducers/selectors, но в итоге это решает больше проблем чем вносит. Для себя вывел главное преимущество это то что в итоге UI = f(x)
, где каждый самый маленький компонент можно протестировать изолировано от всей системы, так и весь поток входящих/исходящих данных/событий в целом между компонентами.
MobX это путь назад к angular с магией под капотом. По началу кажется что вычисляемые значения это круто (привет $watch
), и везде хвалят что вот у нас в отличии от redux не нужно с этим мучатся, но в итоге у нас скрытая реализация в объекте с this.some.get('name')
, в то время как с reselect name = f(x)
.
Ну и самая горячая фича ради которой я готов страдать это тулинг с перемотками состояний. Да в mobx можно отследить изменения объектов, но с redux можно воспроизвести любое состояние приложения (в момент отправки запроса, невалидные формы и т.д).
В «настоящих» проектах мы получаем данные от сервера или пользовательского ввода, форматируем, валидируем, нормализуем и производим другие операции над ними
Расскажите как работаете с формами. С redux после того как думаешь что разобрался со всем, вспоминаешь про формы и снова уходишь на пару дней в транс.
А в чем печалька с формами? Захотел легкой жизни, добавил в проект redux-form. И тут началось. Выпилил. Вернулся к ручному труду. Собираюсь поделиться навыками, пока выписываю в блокнотик тезисы для заметки.
redux-form просто прекрасен. Расширяем, очень продуманный интерфейс, в четыре строки можно завернуть свой инпут, валидаторы как функции, а не магические "required|isEmail|isMagic"
которые непонятно где. Только опять же высокий порог вхождения, но зато опять же это всё окупается в будущем. mobx-react-form похож, но на сложных кейсах не пробовал.
462 открытых issues как бы намекают. Я не смог себя заставить. Кода для обслуживания получается больше, чем без redux-form. И нужно думать не только о поведении формы, но как ее заставить работать с помощью этой прекрасной обертки. В морг.
Самое печальное в redux-form что в итоге компонент формы намертво связан с библиотекой. С другой стороны я не представляю как поддерживать руками свои формы, если их много и они сложный (представим CRM систему).
Вообще вопрос с формами очень важный, и странно что мало качественных решений в react мире (везде?), тот же formsy-react для меня выглядит хуже в плане продуманности API, и вообще решений мало...
А невозможно угодить всем и вся. Это нужна обертка на каждый случай применения. Был у меня подобный печальный опыт с Meteor-овскими велосипедом AutoForm. На первый взгляд — замечательно. Описываешь конфиг и оно само тебе формы выдает! Для двух полей это работает. Но когда формы большие, да со связанными полями. Божечки. Тормозит жутко. Глючит. И опять же вынуждает тебя лезть под капот с кувалдометром. Автор забил на пулл-реквесты. Остаётся форк — вешаешь на саппорт большую кучу "универсального" кода. Оно надо?
стор
export default class FormStore extends ContextStore {
@observable fields = asMap();
@observable defaults = asMap();
@observable errors = asMap();
...
@action updateField = (field, value) => {
this.fields.set(field, value);
}
getFields() {
return this.fields.toJS();
}
getErrors() {
return this.errors.toJS();
}
и HOC
<TextField
type="text"
placeholder="First Name"
name="firstName"
value={props.fields.firstName}
errorMessage={props.errors.firstName}
onChange={props.updateField}
isTransparent={false} />
Минусуйте меня полностью, но не слишком ли дохрена кода для простого открытия меню?
Пробую сейчас mobx у себя на пректе, по сравнению с redux отмечу плюсы:
- Гораздо меньше вспомогательного кода. Фокус на бизнес логике, а не рутине.
- Не нужен глобальный стор, приложение удобнее масштабировать.
- Чтобы сделать при прочих равных redux приложение таким же быстрым как mobx нужно быть Деном Абрамовым.
- Полноценный ООП. Чем сложнее приложение, чем больше сущностей и переиспользуемого кода, тем менее годиться redux. Я бы скорее выбирал не между mobx и redux, а между mobx + react и Angular 2.
- Простые классы, более "натиный" код.
Минусы:
- Больше магии. Меня не напрягает, оно работает)
- Высокие требования к выбору архитектуры. Mobx просто либа, которая не организует потока данных в приложении, в туториалах видел много хардкода.
- Пока небольшое комьюнити, особенно на русском.
В целом статья хорошая, спасибо автору. Со своей строны добавил бы, что не очень нравиться использование стора внутри компонентов, прямой доступ к observerable переменным. В своём приложении я всё это вынес в служебный декоратор, сами компоненты о mobx ничего не знают и получают только чистый js. Не совсем понял смысл использовать класс User, можно было обойтись простой observerable коллекцией, а toggle выест на уровнь выше.
React + mobx путь с нуля. Mobx + react, взгляд со стороны