В «настоящих» проектах мы получаем данные от сервера или пользовательского ввода, форматируем, валидируем, нормализуем и производим другие операции над ними. Всё это принято считать бизнес логикой и должно быть помещено в Model. Так как react — это только треть MVC пирога, для создания пользовательских интерфейсов, то нам потребуется еще что-то для бизнес логики. Некоторые используют паттерны redux или flux, некоторые — backbone.js или даже angular, мы же будем использовать mobx.js в качестве Model.
В предыдущей статье мы уже подготовили фундамент, будем строить на нём. Так как mobx — это standalone библиотека, то для связки с react-ом нам понадобится mobx-react:
npm i --save mobx mobx-react
Кроме того, для работы с декораторами и трансформации свойств классов нам потребуются babel плагины babel-plugin-transform-class-properties и babel-plugin-transform-decorators-legacy:
npm i --save-dev babel-plugin-transform-decorators-legacy babel-plugin-transform-class-properties
Не забудем добавить их в .babelrc
"plugins": [
"react-hot-loader/babel",
"transform-decorators-legacy",
"transform-class-properties"
]
У нас есть компонента Menu, давайте продолжим работу с ней. У панели будет два состояния «открыта/закрыта», а управлять состоянием будем с помощью mobx.
1. Первым делом нам нужно определить состояние и сделать его наблюдаемым посредством добавления декоратора @observable. Состояние может быть представлено любой структурой данных: объектами, массивами, классами и прочими. Создадим хранилище для меню (menu-store.js) в директории stores.
import { observable} from 'mobx';
class MenuStore {
@observable show;
constructor() {
this.show = false;
}
}
export default new MenuStore();
Стор представляет собой ES6 class с единственным свойством show. Мы повесили на него декоратор @observable, тем самым сказали mobx-у наблюдать за ним. Show — это состояние нашей панели, которое мы будем менять.
2. Создать представление, реагирующее на изменение состояния. Хорошо, что у нас уже оно есть, это component/menu/index.js. Теперь, когда состояние будет изменяться, наше меню будет автоматически перересовываться, при этом mobx найдет кротчайший путь для обновления представления. Что бы это произошло, нужно обернуть функцию, описывающую react компонент, в observer.
import React from 'react';
import cn from 'classnames';
import { observer } from 'mobx-react';
/* stores */
import menuStore from '../../stores/menu-store';
/* styles */
import styles from './style.css';
const Menu = observer(() => (
<nav className={cn(styles.menu, { [styles.active]: menuStore.show })}>
<div className={styles['toggle-btn']}>☰</div>
</nav>
));
export default Menu;
В любом react приложении нам понадобится утилита classnames для работы с className. Раньше она входила в пакет react-а, но теперь ставится отдельно:
npm i --save classnames
c её помощью можно склеивать имена классов, используя различные условия, незаменимая вещь.
Видно, что мы добавляем класс «active», если значение состояние меню show === true. Если в конструкторе хранилища поменять состояние на this.show = true, то у панели появится «active» класс.
3. Осталось изменить состояние. Добавим событие click для «гамбургера» в
<div
onClick={() => { menuStore.toggleLeftPanel() }}
className={styles['toggle-btn']}>☰</div>
и метод toggleLeftPanel() в
import { observable } from 'mobx';
class MenuStore {
@observable show;
constructor() {
this.show = false;
}
toggleLeftPanel() {
this.show = !this.show;
}
}
const menuStore = new MenuStore();
export default menuStore;
export { MenuStore };
Note: По дефолту мы экспортируем хранилище как инстанс синглтона, также экспортируется и класс напрямую, так как он тоже может понадобиться, например, для тестов.
Для наглядности добавим стили:
.menu {
position: fixed;
top: 0;
left: -180px;
bottom: 0;
width: 220px;
background-color: tomato;
&.active {
left: 0;
}
& .toggle-btn {
position: absolute;
top: 5px;
right: 10px;
font-size: 26px;
font-weight: 500;
color: white;
cursor: pointer;
}
}
И проверим, что по клику на иконку, наша панель открывается и закрывается. Мы написали минимальный mobx store для управления состоянием панели. Давайте немного нарастим мяса и попробуем управлять панелью из другого компонента. Нам потребуются дополнительные методы для открытия и закрытия панели:
import { observable, computed, action } from 'mobx';
class MenuStore {
@observable show;
constructor() {
this.show = false;
}
@computed get isOpenLeftPanel() {
return this.show;
}
@action('toggle left panel')
toggleLeftPanel() {
this.show = !this.show;
}
@action('show left panel')
openLeftPanel() {
this.show = true;
}
@action('hide left panel')
closeLeftPanel() {
this.show = false;
}
}
const menuStore = new MenuStore();
export default menuStore;
export { MenuStore };
Можно заметить, что мы добавили computed и action декораторы, они обязательны только в strict mode (по умолчанию отключено). Computed значения будут автоматически пересчитаны при изменении соответствующих данных. Рекомендуется использовать action, это поможет лучше структурировать приложение и оптимизировать производительность. Как видно, первым аргументом мы задаём расширенное название производимого действия. Теперь при деббаге мы сможем наблюдать, какой метод был вызван и как менялось состояние.
Note: При разработке удобно использовать расширения хрома для mobx и react, а так же react-mobx devtools
Создадим еще один компонент
import React from 'react';
/* stores */
import menuStore from '../../stores/menu-store';
/* styles */
import styles from './styles.css';
const Component = () => (
<div className={styles.container}>
<button onClick={()=>{ menuStore.openLeftPanel(); }}>Open left panel</button>
<button onClick={()=>{ menuStore.closeLeftPanel(); }}>Close left panel</button>
</div>
);
export default Component;
Внутри пара кнопок, которые будут открывать и закрывать панель. Этот компонент добавим на Home страницу. Должно получиться следующее:
В браузере это будет выглядеть так:
Теперь мы можем управлять состоянием панели не только из самой панели, но и из другого компонента.
Note: если несколько раз произвести одно и тоже действие, например, нажать кнопку «close left panel», то в деббагере можно видеть, что экшен срабатывает, но никакой реакции не происходит. Это значит, что mobx не перересовывает компонент, так как состояние не изменилось и нам не нужно писать «лишний» код, как для pure react компонент.
Осталось немного причесать наш подход, работать со сторами приятно, но разбрасывать импорты хранилищ по всему проекту некрасиво. В mobx-react для таких целей появился Provider (см. Provider and inject) — компонент, который позволяет передавать сторы (и не только) потомкам, используя react context. Для этого обернем корневой компонент app.js в Provider:
import React from 'react';
import { Provider } from 'mobx-react';
import { useStrict } from 'mobx';
/* components */
import Menu from '../components/menu';
/* stores */
import leftMenuStore from '../stores/menu-store';
/* styles */
import './global.css';
import style from './app.css';
useStrict(true);
const stores = { leftMenuStore };
const App = props => (
<Provider { ...stores }>
<div className={style['app-container']}>
<Menu />
<div className={style['page-container']}>
{props.children}
</div>
</div>
</Provider>
);
export default App;
Тут же импортируем все сторы (у нас один) и передаём их провайдеру через props. Так как провайдер работает с контекстом, то сторы будут доступны в любом дочернем компоненте. Также разобьем menu.js компонент на два, чтобы получился «глупый» и «умный» компонент.
import React from 'react';
import cn from 'classnames';
import styles from './style.css';
const Menu = props => (
<nav className={cn(styles.menu, { [styles.active]: props.isOpenLeftPanel })}>
<div onClick={props.toggleMenu}
className={styles['toggle-btn']}>☰</div>
</nav>
);
export default Menu;
import React from 'react';
import { observer, inject } from 'mobx-react';
import Menu from './menu'
const Component = inject('leftMenuStore')(observer(({ leftMenuStore }) => (
<Menu
toggleMenu={() => leftMenuStore.toggleLeftPanel()}
isOpenLeftPanel={leftMenuStore.isOpenLeftPanel} />
)));
Component.displayName = "MenuContainer";
export default Component;
«Глупый» нам не интересен, так как это обычный stateless компонент, который получает через props данные о том открыта или закрыта панель и колбэк для переключения.
Гораздо интереснее посмотреть на его враппер: мы видим тут HOC, где мы инжектим необходимые сторы, в нашем случае «leftMenuStore», в качестве компонента мы передаем наш «глупый компонент», обернутый в observer. Так как мы приинжектили leftMenuStore, то хранилище теперь доступно через props.
практически тоже самое мы проделываем с left-panel-controller:
import React from 'react';
/* styles */
import style from './styles.css';
const LeftPanelController = props => (
<div className={style.container}>
<button onClick={() => props.openPanel()}>Open left panel</button>
<button onClick={() => props.closePanel()}>Close left panel</button>
</div>
);
export default LeftPanelController;
import React from 'react';
import { inject } from 'mobx-react';
import LeftPanelController from './left-panel-controller';
const Component = inject('leftMenuStore')(({ leftMenuStore }) => {
return (
<LeftPanelController
openPanel={() => leftMenuStore.openLeftPanel()}
closePanel={() => leftMenuStore.closeLeftPanel()} />
);
});
LeftPanelController.displayName = 'LeftPanelControllerContainer';
export default Component;
С той лишь разницей, что тут мы не используем observer, так как для этого компонента перерисовавать ничего не требуется, от хранилища нам нужны лишь методы openLeftPanel() и closeLeftPanel().
Note: я использую displayName для задания имени компоненту, это удобно для деббага:
Это все просто, теперь давайте получим данные с сервера, пусть это будет список пользователей с чекбоксами.
Идем на сервер и добавляем роут "/users" для получения пользователей:
const USERS = [
{ id: 1, name: "Alexey", age: 30 },
{ id: 2, name: "Ignat", age: 15 },
{ id: 3, name: "Sergey", age: 26 },
];
...
app.get("/users", function(req, res) {
setTimeout(() => {
res.send(USERS);
}, 1000);
});
Нарочно добавим задержку, чтобы проверить, что приложение работает корректно даже с большим интервалом ответа сервера.
Далее нам понадобится
import { observable, computed, action, asMap, autorun } from 'mobx';
class User {
@observable user = observable.map();
constructor(userData = {}, checked = false) {
this.user.merge(userData);
this.user.set("checked", checked);
}
@computed get userInfo() {
return `${this.user.get("name")} - ${this.user.get("age")}`;
}
@action toggle() {
this.user.set("checked", !this.user.get("checked"));
}
}
class UserStore {
@observable users;
constructor() {
this.users = [];
this.fetch();
}
@computed get selectedCount() {
return this.users.filter(userStore => {
return userStore.user.get("checked");
}).length;
}
getUsers() {
return this.users;
}
@action fetch() {
fetch('/users', { method: 'GET' })
.then(res => res.json())
.then(json => this.putUsers(json));
}
@action putUsers(users) {
let userArray = [];
users.forEach(user => {
userArray.push(new User(user));
});
this.users = userArray;
}
}
const userStore = new UserStore();
autorun(() => {
console.log(userStore.getUsers().toJS());
});
export default userStore;
export { UserStore };
Тут описан класс User со свойством user. В mobx есть observable.map тип данных, он как раз подойдет нам для описания user-а. Грубо говоря, мы получаем наблюдаемый объект, причем, наблюдать можно за изменением конкретного поля. Также становятся доступны getter, setter и прочие вспомогательные методы. Например, в конструкторе с помощью «merge», мы легко можем скопировать поля из userData в user. Это очень удобно, если объект содержит много полей. Также напишем один action для переключения состояния пользователя и вычисляемое значения для получения информации о пользователе.
Ниже описан сам стор, в котором наблюдаемый являемся массив пользователей. В конструкторе мы дергаем метод для получения пользователей с сервера и через action putUsers заполняем пустой массив пользователями. Напоследок, добавим метод, который возвращает вычисляемое количество чекнутых пользователей.
Note: autorun выполняет функцию автоматически, если наблюдаемое значение было изменено. Для примера, тут выводится все пользователи в консоль. Если попробовать достать пользователей методом «getUsers()», то можно заметить, что тип возвращаемых данных не Array, а ObservableArray. Для конвертации observable объектов в javascript структуру, используем toJS().
В app.js не забудем дописать новый user-store, чтобы потомки могли им пользоваться.
Добавим react компоненты в директорию components:
import React from 'react';
import { observer, inject } from 'mobx-react';
import UserList from './user-list';
const Component = inject('userStore')(observer(({ userStore }) => {
return (
<UserList
users={userStore.getUsers()}
selectedUsersCount={userStore.selectedCount} />
);
}));
Component.displayName = 'UserList';
export default Component;
Тут уже привычная нам обертка, передаем массив юзеров и количество чекнутых пользователей через props.
import React from 'react';
/* components */
import UserListItem from './user-list-item';
/* styles */
import style from './styles.css';
const UserList = props => {
return (
<div className={style.container}>
<ul>
{props.users.map(userStore => {
return (
<UserListItem
key={userStore.user.get('id')}
isChecked={userStore.user.get('checked')}
text={userStore.userInfo}
onToggle={() => userStore.toggle()} />);
})}
</ul>
<span>{`Users:${props.users.length}`}</span>
<span>{`Selected users: ${props.selectedUsersCount}`}</span>
</div>
);
};
export default UserList;
Показываем список пользователей и информацию по их количеству. Передаём «toggle()» метод стора через props.
import React from 'react';
const UserListItem = props => (
<li><input type="checkbox" checked={props.isChecked} onClick={() => props.onToggle()} />{props.text}
</li>
);
export default UserListItem;
Рендерим одного пользователя.
Добавляем стили и цепляем готовый компонент на Home страницу. Все готово(github), можно поиграть с чекбоксами и убедиться, что все методы работают.
В итоге мы увидели как работает mobx в связке с react-ом, учитывая все возможности mobx, можно предположить, что такое решение имеет право на жизнь. Mobx прекрасно справляется с обязанностью менеджера состояний для react приложений и предоставляет богатый функционал для реализации.
Комментарии (52)
stifff
20.03.2017 19:12Глупый, наверное вопрос, но можно ли использовать subj в связке с бэкендом на яве?
Если да, то как?
Если нет, то есть ли аналоги, которые можно?justboris
20.03.2017 20:05+2Если у вас бекенд отдает обычный JSON, то какая вам разница, что с ним происходит во фронтенде?
Что с MobX, что с Redux что с ванильным JS все одинаково. Загрузили данные, что-то обновили, отослали обратно.
stifff
21.03.2017 13:46а если не JSON, но Вебсокеты? Есть ли готовые обёртки для реактивности?
mayorovp
21.03.2017 13:48Вам надо искать не какие-то "обертки для реактивности" — а просто парсеры нужного вам формата. Ну или перейти на JSON, это не так сложно.
stifff
21.03.2017 15:43Сорри, я немного неправильно понял — подумалось, что это про JSON, полученный через Ajax.
Мне нужна именно магия для реактивности.
Если она будет работать через JSON, прилетающий по весокету — будет отлично.mayorovp
21.03.2017 15:56+1Кажется, вы все еще не понимаете...
Данные не несут в себе никакого "запаха". Считанные из файла, прилетевшие через AJAX, прилетевние через веб-сокет, введенные пользователем — все они остаются данными. Библиотека работает с ними одинаково.
Библиотека умеет хранить данные, менять данные, строить графы зависимостей и уведомлять об изменении данных.
VolCh
21.03.2017 16:13У ваших объектов есть методы-экшены, которые изменяют их состояние, вызывая нужные вам реакции, как и ререндеринг React UI (через mobx-react), так и вызовы сервера для сохранения (задаете явно) — это магия MobX: после экшена уведомить об изменении всех потребителей объекта, о которых вызывающий экшен не знает в общем случае. Эти экшены дергаете откуда хотите по любым событиям хоть UI, хоть ответу http, хоть сообщению WS, хоть по таймеру, любым доступным в JS способом.
justboris
21.03.2017 13:55+2Обертки, готовые к чему?
Все вышеупомянутые библиотеки не делают никаких ограничений на общение с сервером. В этом и их достоинство в общем-то.
Можно и сокетами, если вам так нужно. Вот как это будет на 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
stifff
21.03.2017 15:41Наверное, я немного плаваю в терминологии и плохо вопрошаю.
Вот есть куча @observable. Некоторые из них я хочу синхронизировать с сервером. Чтобы при изменении на клиенте они тут же отсылались на сервер. И в обратную сторону — при изменении на сервере обновлялся клиент.
В вышеупомянутом LogStore это делается руками. Вот есть ли какая-то библиотека, которая автоматизировала бы процесс синхронизации стейтов между сервером и клиентом?VolCh
21.03.2017 15:46Я для этих целей использую Apollo Client (и Server, но это нюанс), но с MobX он не интегрирован от слова "никак", всё ручками.
justboris
21.03.2017 15:46+1В общем виде в библиотеках магического сохранения данных на сервер нет, потому что это сильно завязано на конкретный способ хранения данных и бизнес-логику. В общем виде это не оформить.
Как вариант, есть Apollo. Это что-то похожее на Meteor, только поверх React и GraphQL. Но, кажется, это не то, что вам нужно.
VolCh
21.03.2017 15:48Если не замахиваться на универсальную "магическую" реализацию, то вполне себе нормально их клиент интегрируется с MobX, включая подписки на обновления по веб-сокетам
stifff
21.03.2017 16:19Благодарю, попробую копнуть в сторону Apollo.
А за универсальность… Ведь у Метеора получилось довольно универсально, через синхронизацию МонгиVolCh
21.03.2017 17:21Не копал Метеор, но "по слухам" это фулл-стэк решение от фронта до монги. Связка же MobX+React от бэкенда никак не зависит, а Apollo требует реализации GraphQL, а они есть не менее чем на десятке языков.
По универсальному MobX+Apollo есть наметки на https://github.com/apollographql/apollo-client/issues/503
stifff
22.03.2017 01:00Вот фулл-стек решение не всегда плохо, мне кажется. Когда нужно на существующий бэкенд добавить реактивности, тогда метеор «не очень». Но когда делается что-то с нуля — ощущается некая разница в сложности подхода.
с одной стороны, у нас есть метеор, который в целом работает.
с другой стороны «небольшой стек» — Java-GraphQL-Apollo-MobX-React который ещё нужно суметь запустить. Ведь в каждом компоненте из списка есть какой-то нюанс.
мне кажется, что порог входа во втором случае на порядок выше, чем в первом. И не понятно почему так. Почему нет более простого решения. Может быть оно никому и не нужно?
На сколько я понял, работая с метеором, в нём нет возможности напрямую сделать реактивную переменную/поле — реактивность завязана на Монгу.
В длинном стеке же можно сделать реактивную переменную, но ценой довольно слоистой архитектуры. Возможно, из-за этого отличия и получается такая разница в сложности…VolCh
22.03.2017 11:30+1Что мне нравится в "«небольшой стек» — Java-GraphQL-Apollo-MobX-React", что зависимости в стэке чисто высокоуровневые, любой компонент стэка можно относительно легко заменить на другой, хоть самописный.
movax10h
21.03.2017 18:02Думаю, вы на правильном пути, вам необходим 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 компоненты, которые будут наблюдать за юзерами, то они автоматически перересуются.stifff
22.03.2017 01:12С «ручным» парсингом прилетевшего обновления — в целом понятно.
Мне почему-то казалось, что есть готовая библиотека с магией, которая сделает за меня вот эту работу:
обработчик на клиенте принимает сообщение, парсит что/как нужно изменить и дергает необходимый метод,
а так же в другую сторону.
Обновилось, например, на каком-то из клиентов firstname, а об этом сразу узнал сервер и потом все клиенты. сами, без дополнительных телодвижений. А потом эти изменения ещё и отобразились в шаблонах. Сами.
Походу, такая магия есть только у Метеора. У остальных — только реактивные переменные.comerc
22.03.2017 14:40такая магия есть только у Метеора. У остальных — только реактивные переменные.
Внезапно такая магия в Метеоре называется ReactiveVar :)
VolCh
20.03.2017 20:07+1Без разницы с чем использовать. Связка mobx+react (как и redux+react, та и вообще любой нормальный фронтенд не делает предположений о том, что на бэкенде). Более того, mobx+react и redux+react вообще с бэкендом не взаимодействуют, они хранят данные в переменных js, изменяют их и отображают изменения. Откуда данные берутся и куда отдаются вне их зоні ответственности.
stifff
21.03.2017 13:45Понял, благодарю.
А если мне хочется реактивности, подобной Метеору, что из готового взять в дополнении к mobx+react?
Чтобы при изменении данных на сервере все клиенты видели их сразу.
Как я понимаю, нужна какая-то обёртка над вебсокетами. Что со стороны фронтенда, что со стороны бэкенда.VolCh
21.03.2017 14:07Грубо, есть какой-то объект order
@observable class Order { status = 'new'; @action set status(value) {this.status = value;} }
В привычном вам обработчике websocket делаете order.status = statusFromWSMessage
yurtaev
20.03.2017 20:10+1MobX выглядит намного "проще" в использование в отличие от redux где необходимо писать больше вспомогательного кода. Но вопрос в том что выбираем, кажущуюся простоту в написание или отсутствие магии в коде с возможностью протестировать почти 100% функционала. Да, не подготовленного человека redux испугает всеми своими actions/reducers/selectors, но в итоге это решает больше проблем чем вносит. Для себя вывел главное преимущество это то что в итоге
UI = f(x)
, где каждый самый маленький компонент можно протестировать изолировано от всей системы, так и весь поток входящих/исходящих данных/событий в целом между компонентами.
MobX это путь назад к angular с магией под капотом. По началу кажется что вычисляемые значения это круто (привет
$watch
), и везде хвалят что вот у нас в отличии от redux не нужно с этим мучатся, но в итоге у нас скрытая реализация в объекте сthis.some.get('name')
, в то время как с reselectname = f(x)
.
Ну и самая горячая фича ради которой я готов страдать это тулинг с перемотками состояний. Да в mobx можно отследить изменения объектов, но с redux можно воспроизвести любое состояние приложения (в момент отправки запроса, невалидные формы и т.д).
В «настоящих» проектах мы получаем данные от сервера или пользовательского ввода, форматируем, валидируем, нормализуем и производим другие операции над ними
Расскажите как работаете с формами. С redux после того как думаешь что разобрался со всем, вспоминаешь про формы и снова уходишь на пару дней в транс.
comerc
21.03.2017 00:44А в чем печалька с формами? Захотел легкой жизни, добавил в проект redux-form. И тут началось. Выпилил. Вернулся к ручному труду. Собираюсь поделиться навыками, пока выписываю в блокнотик тезисы для заметки.
yurtaev
21.03.2017 05:48redux-form просто прекрасен. Расширяем, очень продуманный интерфейс, в четыре строки можно завернуть свой инпут, валидаторы как функции, а не магические
"required|isEmail|isMagic"
которые непонятно где. Только опять же высокий порог вхождения, но зато опять же это всё окупается в будущем. mobx-react-form похож, но на сложных кейсах не пробовал.comerc
21.03.2017 15:15+1462 открытых issues как бы намекают. Я не смог себя заставить. Кода для обслуживания получается больше, чем без redux-form. И нужно думать не только о поведении формы, но как ее заставить работать с помощью этой прекрасной обертки. В морг.
yurtaev
21.03.2017 16:06Самое печальное в redux-form что в итоге компонент формы намертво связан с библиотекой. С другой стороны я не представляю как поддерживать руками свои формы, если их много и они сложный (представим CRM систему).
Вообще вопрос с формами очень важный, и странно что мало качественных решений в react мире (везде?), тот же formsy-react для меня выглядит хуже в плане продуманности API, и вообще решений мало...
comerc
21.03.2017 17:17+2А невозможно угодить всем и вся. Это нужна обертка на каждый случай применения. Был у меня подобный печальный опыт с Meteor-овскими велосипедом AutoForm. На первый взгляд — замечательно. Описываешь конфиг и оно само тебе формы выдает! Для двух полей это работает. Но когда формы большие, да со связанными полями. Божечки. Тормозит жутко. Глючит. И опять же вынуждает тебя лезть под капот с кувалдометром. Автор забил на пулл-реквесты. Остаётся форк — вешаешь на саппорт большую кучу "универсального" кода. Оно надо?
movax10h
21.03.2017 11:32+1Да, всегда так с формами. Видел вот такую реализацию:
стор
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} />
Bellicus
21.03.2017 02:54Минусуйте меня полностью, но не слишком ли дохрена кода для простого открытия меню?
mayorovp
21.03.2017 09:23+2Вот код, необходимый и достаточный для открытия меню:
class MenuStore { @observable show; constructor() { this.show = false; } toggleLeftPanel() { this.show = !this.show; } }
Остальное — разметка и импорты. Что вы видите тут лишнего?
Ну, конструктор, в принципе, можно и убрать, тут согласен...
pelayo
26.03.2017 15:17+4Пробую сейчас mobx у себя на пректе, по сравнению с redux отмечу плюсы:
- Гораздо меньше вспомогательного кода. Фокус на бизнес логике, а не рутине.
- Не нужен глобальный стор, приложение удобнее масштабировать.
- Чтобы сделать при прочих равных redux приложение таким же быстрым как mobx нужно быть Деном Абрамовым.
- Полноценный ООП. Чем сложнее приложение, чем больше сущностей и переиспользуемого кода, тем менее годиться redux. Я бы скорее выбирал не между mobx и redux, а между mobx + react и Angular 2.
- Простые классы, более "натиный" код.
Минусы:
- Больше магии. Меня не напрягает, оно работает)
- Высокие требования к выбору архитектуры. Mobx просто либа, которая не организует потока данных в приложении, в туториалах видел много хардкода.
- Пока небольшое комьюнити, особенно на русском.
В целом статья хорошая, спасибо автору. Со своей строны добавил бы, что не очень нравиться использование стора внутри компонентов, прямой доступ к observerable переменным. В своём приложении я всё это вынес в служебный декоратор, сами компоненты о mobx ничего не знают и получают только чистый js. Не совсем понял смысл использовать класс User, можно было обойтись простой observerable коллекцией, а toggle выест на уровнь выше.
comerc
Как обычно, хочется услышать несколько слов, почему MobX а не Redux. Для непосвященных.
VolCh
Вообще туториалы типа "как настроить с нуля такую-то связку" не предполагают раскрытия вопроса "почему эту связку, а не конкурирующую".
comerc
Так я же предлагаю развернуть дискуссию не в самой статье, а в комментариях.
yogurt1
Какой смысл в таких статьях, если они быстро устаревают? Куда проще почитать офф. доки нужной либы
Лучше бы раскрыли, какой смысл в Mobx
Мне вот очень нравится Mobx, но смущает его магия. Свободного времени сложно найти на то, что бы разобраться в его магии и разобраться, где стоит использовать мобикс, а где нет. Было бы приятно почитать статью на эту тему
movax10h
Смысл mobx — управлять состояниями.
Я вас правильно понял, что вы можете прочитать доки, но у вас времени нет, но статьи бесполезны?
kahi4
MobX, RxJS и Redux являютя, по сути, различными идеями реализации реактивного программирования (pull, push и что-то там особое), каждое из которых дает свое преимущество и недостатки (а еще их часто между собой комбинируют).
MobX, в отличие от redux, больше подходит для построения MVVM архитектуры приложения и хорошо структуризированного ООП, когда redux стремится в функциональщину и нормальным сторам. За счет того, что он (mobx) скрывает в себе много кода по обновлению компонентов, инкапсулирует логику обновления компонентов в себе (в хорошем mobx приложении вы ни разу не должны прописывать shouldComponentUpdate), при этом перерисовывает только непосредственные зависимости в отличие от перерисовки всего View в редуксе, ускоряет и облегчает разработку, требует меньше оверхеда в виде постоянного создавания всевозможных action и reducer, а так же увеличивает отзывчивость страницы. В теории.
Я бы даже сказал так: в очень оторванной от действительности теории, покуда на практике красивый код из todo-mvc примера превращается в достаточно запутанный VM слой с геморроем при lazy-загрузке, постоянными лишними перерисовками, которые устанешь вычищать, а так же хитрыми костылями там, где он по какой-то причине предпочел работать не так, как тебе нужно. Зато на нем действительно получается изящная MVVM архитектура.
strannik_k
На днях наконец-то нашлось время ознакомиться с Mobx.
Сначала тоже наткнулся на проблему с лишними перерисовками, но потом прочитал, что она решается использованием транзакций с помощью action и runInAction — https://mobx.js.org/refguide/transaction.html. Не знаю, может быть вы их использовали и у вас все-равно были лишние перерисовки по другой причине.Насчет архитектуры приложений, использующих MobX.
Из того, что я прочитал/попробовал, не вижу проблем делать архитектуру с MobX как душе угодно. Главное, что можно сделать любые объекты наблюдаемыми и, использовав соответствующие поля этих объектов в autorun, action, computed или render, автоматически подписаться на их изменения.
Можно сделать один большой стор, можно несколько маленьких. Можно однонаправленный поток данных, можно MVVM, можно заодно и стейты компонентов заменить на observable объекты. Также нет необходимости писать методы в сторах. Можно оставить только данные, ну и вычисляемые значения. Хотя @computed, как и action можно вынести в любое место программы. Нет необходимости писать их в том же наблюдаемом объекте.
Недавно начал новый проект. Сейчас решил заменить в нем нынешнюю библиотеку управления состоянием на MobX. В моем случае не вижу необходимости менять существующую архитектуру.
indestructable
Для меня лично MobX стойко ассоциируется с Knockout и всеми его болячками.
Плюс не нравится описывать структуру данных модели и загрузку ее данными сервера, как было в Нокауте. Хотя, может быть, в МобХ такой проблемы и нет.