Настройка State Management (MobX) для React-приложения
MobX использует реактивное программирование: наблюдаемые (observable) данные, вычисляемые значения (computed) и реакции (autorun, reaction, when). Компоненты, обёрнутые в observer, автоматически подписываются на ровно те наблюдаемые, которые читают при рендере — не больше.
MobX выигрывает там, где состояние сложное, объектно-ориентированное или когда нужно минимизировать ручную работу по оптимизации ре-рендеров.
Что входит в работу
Проектирование стор-классов, настройка декораторов или makeObservable/makeAutoObservable, интеграция с React через mobx-react-lite, реакции и сайд-эффекты, DevTools, персистентность, тестирование сторов.
Установка
npm install mobx mobx-react-lite
При использовании TypeScript с декораторами нужно включить в tsconfig.json:
{
"compilerOptions": {
"experimentalDecorators": true,
"useDefineForClassFields": false
}
}
Стор через makeAutoObservable
Самый лаконичный способ — makeAutoObservable. MobX сам определяет, что наблюдаемое, что вычисляемое, что экшен:
import { makeAutoObservable, runInAction } from 'mobx'
class CartStore {
items: CartItem[] = []
loading = false
error: string | null = null
constructor() {
makeAutoObservable(this)
}
// computed
get total() {
return this.items.reduce((sum, i) => sum + i.price * i.quantity, 0)
}
get count() {
return this.items.reduce((sum, i) => sum + i.quantity, 0)
}
// action
addItem(product: Product) {
const existing = this.items.find((i) => i.id === product.id)
if (existing) {
existing.quantity++
} else {
this.items.push({ ...product, quantity: 1 })
}
}
removeItem(id: string) {
this.items = this.items.filter((i) => i.id !== id)
}
clearCart() {
this.items = []
}
// async action — мутации внутри runInAction
async checkout() {
this.loading = true
this.error = null
try {
await api.post('/orders', { items: this.items })
runInAction(() => {
this.items = []
this.loading = false
})
} catch (err) {
runInAction(() => {
this.error = err instanceof Error ? err.message : 'Ошибка оформления'
this.loading = false
})
}
}
}
export const cartStore = new CartStore()
Декораторы (альтернативный синтаксис)
import { observable, computed, action, makeObservable } from 'mobx'
class UserStore {
@observable user: User | null = null
@observable token: string | null = null
constructor() {
makeObservable(this)
}
@computed get isAuthenticated() {
return this.token !== null
}
@action setUser(user: User, token: string) {
this.user = user
this.token = token
}
@action logout() {
this.user = null
this.token = null
}
}
Интеграция с React через Context
import { createContext, useContext } from 'react'
interface RootStore {
cart: CartStore
user: UserStore
ui: UIStore
}
const rootStore: RootStore = {
cart: new CartStore(),
user: new UserStore(),
ui: new UIStore(),
}
const StoreContext = createContext<RootStore>(rootStore)
export const StoreProvider = ({ children }: { children: React.ReactNode }) => (
<StoreContext.Provider value={rootStore}>{children}</StoreContext.Provider>
)
export const useStore = () => useContext(StoreContext)
import { observer } from 'mobx-react-lite'
const CartButton = observer(() => {
const { cart } = useStore()
return (
<button onClick={() => cart.checkout()} disabled={cart.loading}>
{cart.loading ? 'Оформление...' : `Оформить (${cart.count} шт, ${cart.total} ₽)`}
</button>
)
})
observer — HOC из mobx-react-lite, который превращает компонент в реактивный. Без него MobX-состояние не будет вызывать ре-рендер.
Реакции и сайд-эффекты
import { autorun, reaction, when } from 'mobx'
// autorun: запускается немедленно и при каждом изменении зависимостей
const dispose = autorun(() => {
document.title = `Корзина (${cartStore.count})`
})
// reaction: следит за первой функцией, запускает вторую при изменении
reaction(
() => userStore.token,
(token) => {
if (token) {
localStorage.setItem('auth_token', token)
} else {
localStorage.removeItem('auth_token')
}
}
)
// when: однократно выполняется, когда условие становится true
when(
() => cartStore.count > 10,
() => notificationStore.show('Много товаров — оформите заказ!')
)
Все три возвращают dispose-функцию — важно вызывать её при размонтировании компонента или уничтожении стора.
Персистентность через makePersistable
npm install mobx-persist-store
import { makePersistable } from 'mobx-persist-store'
class ThemeStore {
theme: 'light' | 'dark' = 'light'
language = 'ru'
constructor() {
makeAutoObservable(this)
makePersistable(this, {
name: 'ThemeStore',
properties: ['theme', 'language'],
storage: window.localStorage,
})
}
setTheme(theme: 'light' | 'dark') {
this.theme = theme
}
}
MobX DevTools
npm install mobx-devtools-mst # или используем встроенный spy
Для отладки без дополнительных пакетов:
import { spy } from 'mobx'
if (process.env.NODE_ENV === 'development') {
spy((event) => {
if (event.type === 'action') {
console.log(`[MobX] ${event.name}`, event.arguments)
}
})
}
Тестирование
MobX-сторы — обычные классы, тестируются без React:
import { CartStore } from '../stores/CartStore'
describe('CartStore', () => {
let store: CartStore
beforeEach(() => {
store = new CartStore()
})
it('корректно считает total', () => {
store.addItem({ id: '1', name: 'Test', price: 100 })
store.addItem({ id: '1', name: 'Test', price: 100 })
expect(store.count).toBe(2)
expect(store.total).toBe(200)
})
it('clearCart сбрасывает всё', () => {
store.addItem({ id: '1', name: 'Test', price: 100 })
store.clearCart()
expect(store.items).toHaveLength(0)
expect(store.total).toBe(0)
})
})
Структура проекта
src/
stores/
CartStore.ts
UserStore.ts
UIStore.ts
RootStore.ts # агрегирует все сторы
context/
StoreContext.tsx
hooks/
useStore.ts
Особенности и типичные ошибки
Мутировать state вне action в strict mode нельзя — MobX выбросит предупреждение. Включаем strict mode явно:
import { configure } from 'mobx'
configure({
enforceActions: 'always',
computedRequiresReaction: true,
})
computedRequiresReaction: true запрещает читать computed-значения вне реактивного контекста — помогает поймать случайные обращения к стору в не-observer компонентах.
Дочерние компоненты должны быть observer независимо от родителя — реактивность не наследуется автоматически.
Что делаем
Проектируем иерархию сторов, настраиваем strict mode и конфигурацию MobX, интегрируем с React через Context, настраиваем реакции для сайд-эффектов (синхронизация с localStorage, аналитика, заголовок страницы), покрываем тестами бизнес-логику в сторах.
Срок: 2–4 дня в зависимости от сложности предметной области.







