В жизни можно бесконечно делать 3 вещи, смотреть как горит огонь, как течет вода и как я пытаюсь агитировать переписать react приложение с Context API на что-то более функциональное, на чем можно построить нормальную архитектуру приложения. Последнее время я все чаще выбираю mobx, поэтому поговорим сегодня про него.

Так что же делать, если приложение уже написано, а с чего-то начать надо?

Предположим, у нас есть компонент, который обращается к глобальному стейту приложения.

import { FC, useContext, createContext } from 'react';

const AppContext = createContext({ counter: 0, onIncrement: () => {}});

const AppContextProvider: FC = ({ children }) => {
  const [counter, setCounter] = useState(0);
  return <AppContext.Provider value={{
	counter, onIncrement: () => setCounter(v => v + 1)
  }}>{children}</AppContext.Provider>
}

const Counter: FC<{}> = () => {
   const { counter, onIncrement } = useContext(AppContext);

   return <button onClick={onIncrement}>Click {counter}</div>
}

Опишем модель, которая будет заменять функциональность глобального стейта (необязательно всю, слона лучше есть по-частям).

import { FC, createContext } from 'react';
import { _isComputingDerivation, make, observable, action } from 'mobx';

export class AppModel {
  counter = 0;
  constructor() {
	  makeObservable(this, {
	    text: observable,
	    setText: action
	  })
  }
  onIncrement = () => { this.counter += 1; }
}

export const AppMobxContext = createContext(new AppModel());

export const AppMobxContextProvider: FC<{ model: AppModel }> = ({ model, children }) =>
	<AppMobxContext.Provider value={model}>
		{children}
	</AppMobxContext.Provider>

export const useAppModel = () => {

  if (process.env.NODE_ENV === 'development') {
    // если хук вызван вне обернутого компонента то сразу кидаем ошибку в режиме разработки
	  if (!_isComputingDerivation()) { // здесь мы пр
	    throw new Error('compoment should wrap with observer from "mobx-react-lite"');
	  }
  }

  return useContext(AppMobxContext);
}

Теперь мы можем переписать наш компонент, удаляя ненужное данные из глобального стейта.

import { observer } from 'mobx-react-lite';
const Component: FC<{}> = observer(() => {
   const { counter, onIncrement } = useAppModel();
   const {} = useContext(AppContext); // все что не было заменено, будет продолжать работать

   return <button onClick={onIncrement}>Click {counter}</div>
});

И последним шагом когда компонент полностью очиститься от влияния глобального контекста просто переносим передачу в модели в props компонента.

import { observer } from 'mobx-react-lite';
const Component: FC<{ model: AppModel }> = observer(({ counter, onIncrement }) => {
   return <button onClick={onIncrement}>Click {counter}</div>
});