Comments 22
Спасибо за статью в популяризацию MobX, но многое не соответствует действительности.
MobX - не библиотека глобальных сторов, и не библиотека локальных. Это система реактивности, когда можно подписаться на изменение параметров объекта.
const data = observable({ test: 1 });
autorun(() => console.log(data.test));
data.test = 2;
// в консоли 1 и затем 2
Но использовать этот механизм во фреймворках можно, в том числе для менеджеринга состояния (и в многочисленных других кейсах).
"библиотека, использующая в своей реализации Context API" - нет, MobX не привязан к фреймворкам типа React и не тянет их зависимостью. Но может быть использован во фреймворках, в том числе для передачи его реактивных объектов в React Context. Можно и через другие провайдеры и инджекты.
"Mobx сторы хорошо масштабируются" - масштабируются архитектурные подходы, а MobX просто реактивная обвязка над объектами.
"Mobx стор состоит из двух частей — самого стора с данными и провайдера этого стора" - только первое) Провайдер к фреймворкам - это отдельная история. Лучше написать что "чтобы подключить к React через Context нужно завести кроме стора еще и провайдер".
"Стор в сущности функция‑генератор, которая возвращает объект" - нет, это может быть просто объект как в моем примере выше, но самый эффективный подход - оформлять сторы в виде классов
class Store {
constructor() { makeAutoObservable(this); }
data = 1;
toggle = () => { this.data = 2 }
}
const store = new Store();
Таким образом этот стор сам по себе реактивный и типизированный.
Если делать классами, то соответственно будет биндинг контекста и не придется писать
<button onClick={() => appStore.toggleTest()}>
вызывая лишние ререндеры button, потому что анонимная функция будет создаваться новая на каждый рендер. А нужно будет писать
<button onClick={appStore.toggleTest}>
сохраняя равенство по ссылкам и оптимизируя перфоманс процесса reconciliation Реакта.
Насчет примера из п.6 добавлю, что toggleTest надо сделать именно стрелочной функцией в классе, и с соответствующими параметрами (либо без параметров).
либо использовать одну из многих либ для автобиндинга, если это не стрелочная функция - в mobx тоже есть autoBind: true параметр, если нужно. Но лучше приучаться писать в методах класса стрелочные функции, так не нужны дополнительные либы и обертки - чисто семантика языка
И для любителей декораторов есть action
.bound
не нужно иметь дело ни с какими редисерами и мидлварами.
Pipe сквозь middlewares мной использовалось часто, так, что это нифига не плюс.
Mobx сторы хорошо масштабируются
У вас есть личный опыт? Мне очень интересно как там с маштабируемостью у больший веб-приложений, использующий MobX. Поделитесь, пожалуйста)
C 2016 года юзаю React + MobX по сей день и на огромных вэб-приложениях, реально огромных вообще 0 проблем, всё шикарно работает, быстро и стабильно!
И вот как им пользоваться - https://codesandbox.io/s/adoring-banach-k8m9ss?file=/src/App.tsx
А вы используете через Context Api? Или без?
А вы используете через Context Api? Или без?
Конечно без него. По ссылке выше я же показал как надо пользоваться связкой react+mobx. Context Api я использую только по назначению, а именно:
У нас есть компонент, допустим большая форма, разбитая на несколько блоков, и у этой формы должно быть свое общее локальное состояние, вот тогда я использую Context Api с MobX'ом и получается такой код:
export const BigForm = observer(() => {
const [state] = useState(() => new BigFormState());
return (
<BigFormContext.Provider value={state}>
<Header />
<ImageUploader />
<Editor />
<Contacts />
<Footer />
</BigFormContext.Provider>
)
})
я использую вот https://github.com/Iverson/mobx-react-viewmodel хелпер для подобных кейсов из бонусов там есть возможность отписаться от reacton
почему не useLocalStore? зачем реактовский стейт? Если каждая секция формы будет обёрнута в свой обсервер - то при изменении одного поля "передёргиваться" должна только соответствующая секция, а не вся форма)
Если каждая секция формы будет обёрнута в свой обсервер - то при изменении одного поля "передёргиваться" должна только соответствующая секция, а не вся форма)
Так и будет, при изменении одного поля "передёргиваться" будет только соответствующая секция, а не вся форма. Это же очевидно и проверяется на раз-два - https://codesandbox.io/s/gracious-morning-tmgzyd?file=/src/App.tsx
почему не useLocalStore?
Это тоже самое.
зачем реактовский стейт?
Обратите внимание, на то, что useState принимает внутри себя функцию, а не просто значение, это 2 совершенно разных поведения.
Это тоже самое.
Думал может есть какая-то скрытая разница)
Просто на работе форсю использовать везде useLocalStore, а useState только в предельно "тупых" компонентах у которых нет детей, состояние одно и в нём примитив, и они ничего не знают про mobx (условно, чтоб можно было копипастить между проектами) .
А если у вас много сторов, и они в разных частях приложения, и например нужно из одного стора использовать данные в другом, вы как поступаете в таком случае? Как шарить данные между сторами ?
Прямо в лоб, как есть, напрямую. Единственное если 2 класса синхронно в момент инициализации первоначальной пытаются дернуть друг друга. то будет ошибка т.к. один из них будет undefiend в этот момент. Для этого нужно просто в setTimeout/Promise.then завернуть эти инициализированные вызовы.
https://codesandbox.io/s/angry-mopsa-vx6cgg?file=/src/App.tsx
Мы делаем через корневой стор. Очень удобно. Можно внутри одного стора следить\реагировать за данными другого стора. Реакт вообще не учувствует в шаринге данных, он только рисует итоговый результат (собственно чем и должен заниматься шаблонизатор).
const StoresContext = createContext();
class RootStore {
constructor() {
this.service1 = new Service1(this);
this.service2 = new Service2(this);
this.service3 = new Service3(this);
// внутри сервисы через rootStore доступны друг другу
// и с помощью mobx-реакций могут внутри себя подписаться на другие сервисы
}
}
export const StoresProvider = (props) => {
const rootStore = new RootStore();
return (
<StoresContext.Provider value={rootStore}>
{props.children}
</StoresContext.Provider>
);
};
export const useStore = () => {
return useContext(StoresContext);
};
Мы делаем через корневой стор. Очень удобно.
Удобнее конечно импортировать напрямую и не использовать лишние обертки в виде контекстов не по назначению) Код будет чище) Глобальное состояние на то и глобальное, что только 1 раз инициализируется и используется везде в приложении)
и не использовать лишние обертки
в нашем случае лишняя корневая обёртка позволяет добавить флажок что все сторы-сервисы проинициализированны и готовы, против setTimeout-ов при лобовых импортах)) плюс другие некоторые подготовительные действия которые не хочется пихать ни в один сервис.
т.е. стартовый флоу приложения вцелом больше react-way как-бы.
В вашей практике вы используйте reaction и autorun в локальных сторах на для реакта, либо полностью обходитесь useEffect? Как я понял, reaction не подходит для объявления в конструкторе для локального стора в классе, хотелось бы полностью избавиться от useEffect, так как если вся логика приложения на мобикс, то хотелось бы полностью полагаться на мобиксовские инструменты
Как я понял, reaction не подходит для объявления в конструкторе для локального стора в классе
Вполне подходит, если использовать обертки вроде той ссылки на гитхаб, что была выше (у нас похожая утилита на проекте). useEffect остаётся под капотом.
В любом случае если есть необходимость именно reaction и autorun В локальных сторах использовать, то без useEffect никуда не уйти, вопрос в том как именно будет выглядить код, вот так
useEffect(() => {
return reaction(() => [...], () => {...})
}, [])
или вот так
useEffect(localState.someLogic, [])
где localState.someLogic
class LocalState {
someLogic = () => {
return reaction(() => [...], () => {...})
}
}
или же у вас будет кастомный хук для инициализации локальных состояний, который при анмаунте будет дергать localState.unmount() в котором будут происходить диспоузы reaction и autorun, clearTimeout и т.п. Или же можно ещё более хитрую логику написать, короче тут большой разгул по реализации
Mobx: библиотека глобальных сторов (state manager)