Как стать автором
Обновить

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

Как обычно, хочется услышать несколько слов, почему MobX а не Redux. Для непосвященных.

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

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

Какой смысл в таких статьях, если они быстро устаревают? Куда проще почитать офф. доки нужной либы
Лучше бы раскрыли, какой смысл в Mobx
Мне вот очень нравится Mobx, но смущает его магия. Свободного времени сложно найти на то, что бы разобраться в его магии и разобраться, где стоит использовать мобикс, а где нет. Было бы приятно почитать статью на эту тему

Смысл mobx — управлять состояниями.
Куда проще почитать офф. доки

Свободного времени сложно найти на то, что бы разобраться в его магии

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

MobX, RxJS и Redux являютя, по сути, различными идеями реализации реактивного программирования (pull, push и что-то там особое), каждое из которых дает свое преимущество и недостатки (а еще их часто между собой комбинируют).


MobX, в отличие от redux, больше подходит для построения MVVM архитектуры приложения и хорошо структуризированного ООП, когда redux стремится в функциональщину и нормальным сторам. За счет того, что он (mobx) скрывает в себе много кода по обновлению компонентов, инкапсулирует логику обновления компонентов в себе (в хорошем mobx приложении вы ни разу не должны прописывать shouldComponentUpdate), при этом перерисовывает только непосредственные зависимости в отличие от перерисовки всего View в редуксе, ускоряет и облегчает разработку, требует меньше оверхеда в виде постоянного создавания всевозможных action и reducer, а так же увеличивает отзывчивость страницы. В теории.


Я бы даже сказал так: в очень оторванной от действительности теории, покуда на практике красивый код из todo-mvc примера превращается в достаточно запутанный VM слой с геморроем при lazy-загрузке, постоянными лишними перерисовками, которые устанешь вычищать, а так же хитрыми костылями там, где он по какой-то причине предпочел работать не так, как тебе нужно. Зато на нем действительно получается изящная MVVM архитектура.

На днях наконец-то нашлось время ознакомиться с Mobx.

постоянными лишними перерисовками
Сначала тоже наткнулся на проблему с лишними перерисовками, но потом прочитал, что она решается использованием транзакций с помощью action и runInAction — https://mobx.js.org/refguide/transaction.html. Не знаю, может быть вы их использовали и у вас все-равно были лишние перерисовки по другой причине.

Насчет архитектуры приложений, использующих MobX.
Из того, что я прочитал/попробовал, не вижу проблем делать архитектуру с MobX как душе угодно. Главное, что можно сделать любые объекты наблюдаемыми и, использовав соответствующие поля этих объектов в autorun, action, computed или render, автоматически подписаться на их изменения.

Можно сделать один большой стор, можно несколько маленьких. Можно однонаправленный поток данных, можно MVVM, можно заодно и стейты компонентов заменить на observable объекты. Также нет необходимости писать методы в сторах. Можно оставить только данные, ну и вычисляемые значения. Хотя @computed, как и action можно вынести в любое место программы. Нет необходимости писать их в том же наблюдаемом объекте.

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

Для меня лично MobX стойко ассоциируется с Knockout и всеми его болячками.


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

Глупый, наверное вопрос, но можно ли использовать subj в связке с бэкендом на яве?
Если да, то как?
Если нет, то есть ли аналоги, которые можно?

Если у вас бекенд отдает обычный JSON, то какая вам разница, что с ним происходит во фронтенде?


Что с MobX, что с Redux что с ванильным JS все одинаково. Загрузили данные, что-то обновили, отослали обратно.

а если не JSON, но Вебсокеты? Есть ли готовые обёртки для реактивности?

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

Сорри, я немного неправильно понял — подумалось, что это про JSON, полученный через Ajax.

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

Кажется, вы все еще не понимаете...


Данные не несут в себе никакого "запаха". Считанные из файла, прилетевшие через AJAX, прилетевние через веб-сокет, введенные пользователем — все они остаются данными. Библиотека работает с ними одинаково.


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

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

Есть прекрасная статья, как организовать магию реактивности. Но на redux-е. Тынц.

У ваших объектов есть методы-экшены, которые изменяют их состояние, вызывая нужные вам реакции, как и ререндеринг 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

Вместо вызова bind не лучше ли использовать action.bound?

Я не настоящий MobX-ер. Может быть и лучше.


Что нашел в интернете, то показываю, проект по ссылке не мой.

Наверное, я немного плаваю в терминологии и плохо вопрошаю.

Вот есть куча @observable. Некоторые из них я хочу синхронизировать с сервером. Чтобы при изменении на клиенте они тут же отсылались на сервер. И в обратную сторону — при изменении на сервере обновлялся клиент.

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

Я для этих целей использую Apollo Client (и Server, но это нюанс), но с MobX он не интегрирован от слова "никак", всё ручками.

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


Как вариант, есть Apollo. Это что-то похожее на Meteor, только поверх React и GraphQL. Но, кажется, это не то, что вам нужно.

Если не замахиваться на универсальную "магическую" реализацию, то вполне себе нормально их клиент интегрируется с MobX, включая подписки на обновления по веб-сокетам

Благодарю, попробую копнуть в сторону Apollo.

А за универсальность… Ведь у Метеора получилось довольно универсально, через синхронизацию Монги

Не копал Метеор, но "по слухам" это фулл-стэк решение от фронта до монги. Связка же MobX+React от бэкенда никак не зависит, а Apollo требует реализации GraphQL, а они есть не менее чем на десятке языков.


По универсальному MobX+Apollo есть наметки на https://github.com/apollographql/apollo-client/issues/503

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

с одной стороны, у нас есть метеор, который в целом работает.
с другой стороны «небольшой стек» — Java-GraphQL-Apollo-MobX-React который ещё нужно суметь запустить. Ведь в каждом компоненте из списка есть какой-то нюанс.

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

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

Что мне нравится в "«небольшой стек» — Java-GraphQL-Apollo-MobX-React", что зависимости в стэке чисто высокоуровневые, любой компонент стэка можно относительно легко заменить на другой, хоть самописный.

Думаю, вы на правильном пути, вам необходим WebSocket. Приблизительно это выглядит так, например, кто-то поменял данные о пользователе, и сервер получил эту информацию. Тогда через сокеты сервер отсылает всем клиентам сообщение вида
 {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.

Знаете такие же, но не заброшенные?

Сегодня выложили видео доклада https://youtu.be/1RMhUPsVw2M

такая магия есть только у Метеора. У остальных — только реактивные переменные.

Внезапно такая магия в Метеоре называется ReactiveVar :)

Без разницы с чем использовать. Связка mobx+react (как и redux+react, та и вообще любой нормальный фронтенд не делает предположений о том, что на бэкенде). Более того, mobx+react и redux+react вообще с бэкендом не взаимодействуют, они хранят данные в переменных js, изменяют их и отображают изменения. Откуда данные берутся и куда отдаются вне их зоні ответственности.

Понял, благодарю.

А если мне хочется реактивности, подобной Метеору, что из готового взять в дополнении к mobx+react?
Чтобы при изменении данных на сервере все клиенты видели их сразу.
Как я понимаю, нужна какая-то обёртка над вебсокетами. Что со стороны фронтенда, что со стороны бэкенда.

Зачем вам обертка над вебсокетами?

Грубо, есть какой-то объект order


@observable
class Order {
  status = 'new';
  @action set status(value) {this.status = value;} 
}

В привычном вам обработчике websocket делаете order.status = statusFromWSMessage

Нет, так нельзя делать. Декоратор @observable применяется к свойствам, а не к классам.


Вот так правильно:


class Order {
  @observable status = 'new';
}

Ну да. Перепутал с обсервер, с головы когда писал :)

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} />

Минусуйте меня полностью, но не слишком ли дохрена кода для простого открытия меню?

Вот код, необходимый и достаточный для открытия меню:


class MenuStore {
  @observable show;

  constructor() {
    this.show = false;
  }

  toggleLeftPanel() {
    this.show = !this.show;
  }
}

Остальное — разметка и импорты. Что вы видите тут лишнего?


Ну, конструктор, в принципе, можно и убрать, тут согласен...

Пробую сейчас mobx у себя на пректе, по сравнению с redux отмечу плюсы:


  1. Гораздо меньше вспомогательного кода. Фокус на бизнес логике, а не рутине.
  2. Не нужен глобальный стор, приложение удобнее масштабировать.
  3. Чтобы сделать при прочих равных redux приложение таким же быстрым как mobx нужно быть Деном Абрамовым.
  4. Полноценный ООП. Чем сложнее приложение, чем больше сущностей и переиспользуемого кода, тем менее годиться redux. Я бы скорее выбирал не между mobx и redux, а между mobx + react и Angular 2.
  5. Простые классы, более "натиный" код.

Минусы:


  1. Больше магии. Меня не напрягает, оно работает)
  2. Высокие требования к выбору архитектуры. Mobx просто либа, которая не организует потока данных в приложении, в туториалах видел много хардкода.
  3. Пока небольшое комьюнити, особенно на русском.

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

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

Привет! А можно попросить Вас это показать? Сам декоратор и пример его использования.

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

Публикации

Истории