Pull to refresh

Comments 22

Спасибо за статью в популяризацию MobX, но многое не соответствует действительности.

  1. MobX - не библиотека глобальных сторов, и не библиотека локальных. Это система реактивности, когда можно подписаться на изменение параметров объекта.

const data = observable({ test: 1 }); 
autorun(() => console.log(data.test)); 
data.test = 2; 
// в консоли 1 и затем 2

Но использовать этот механизм во фреймворках можно, в том числе для менеджеринга состояния (и в многочисленных других кейсах).

  1. "библиотека, использующая в своей реализации Context API" - нет, MobX не привязан к фреймворкам типа React и не тянет их зависимостью. Но может быть использован во фреймворках, в том числе для передачи его реактивных объектов в React Context. Можно и через другие провайдеры и инджекты.

  2. "Mobx сторы хорошо масштабируются" - масштабируются архитектурные подходы, а MobX просто реактивная обвязка над объектами.

  3. "Mobx стор состоит из двух частей — самого стора с данными и провайдера этого стора" - только первое) Провайдер к фреймворкам - это отдельная история. Лучше написать что "чтобы подключить к React через Context нужно завести кроме стора еще и провайдер".

  4. "Стор в сущности функция‑генератор, которая возвращает объект" - нет, это может быть просто объект как в моем примере выше, но самый эффективный подход - оформлять сторы в виде классов

class Store { 
  constructor() { makeAutoObservable(this); }

  data = 1;

  toggle = () => { this.data = 2 } 
}

const store = new Store();

Таким образом этот стор сам по себе реактивный и типизированный.

  1. Если делать классами, то соответственно будет биндинг контекста и не придется писать

<button onClick={() => appStore.toggleTest()}>

вызывая лишние ререндеры button, потому что анонимная функция будет создаваться новая на каждый рендер. А нужно будет писать

<button onClick={appStore.toggleTest}>

сохраняя равенство по ссылкам и оптимизируя перфоманс процесса reconciliation Реакта.

Насчет примера из п.6 добавлю, что toggleTest надо сделать именно стрелочной функцией в классе, и с соответствующими параметрами (либо без параметров).

либо использовать одну из многих либ для автобиндинга, если это не стрелочная функция - в mobx тоже есть autoBind: true параметр, если нужно. Но лучше приучаться писать в методах класса стрелочные функции, так не нужны дополнительные либы и обертки - чисто семантика языка

И для любителей декораторов есть action.bound

не нужно иметь дело ни с какими редисерами и мидлварами.

Pipe сквозь middlewares мной использовалось часто, так, что это нифига не плюс.

Думаю, имелось в виду именно в контексте Redux про миддлвары. Иначе это нападка на Promise, который имеет паттерн миддлвара, или Express.

Mobx сторы хорошо масштабируются

У вас есть личный опыт? Мне очень интересно как там с маштабируемостью у больший веб-приложений, использующий MobX. Поделитесь, пожалуйста)

А вы используете через 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>
  )
})

почему не 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 и т.п. Или же можно ещё более хитрую логику написать, короче тут большой разгул по реализации

Sign up to leave a comment.

Articles