Что такое MobX и как его использовать
Краткое руководство по использованию MobX в React: основные концепции, подключение, пример списка задач с автосохранением и рекомендации по типичным ошибкам.
Когда компонент часто пересчитывается или Redux-хранилище становится избыточным, удобнее использовать MobX. Он описывает состояние декларативно, а реактивность обеспечивает мгновенные и предсказуемые обновления. Далее показано подключение MobX к React-проекту и пример списка задач с автосохранением.
Что такое MobX и как он работает
В ядре MobX три простых понятия: observable, action и reaction (или autorun). Observable-объекты хранят данные, а любые изменения в них автоматически “прокидываются” в компоненты, помеченные как observer
. Action-функции группируют изменения, чтобы реактивная система могла отслеживать их как один шаг. Reaction/autorun это “слушатели”, которые вызываются, когда зависимые observable меняются.
Концепт | Что делает | Пример |
---|---|---|
observable | Делает данные реактивными | const count = observable.box(0); |
computed | Кеширует производные значения | const total = computed(() => items.reduce((s,i)=>s+i.price,0)); |
action | Группирует изменения | const add = action(item => items.push(item)); |
reaction/autorun | Реагирует на изменения | autorun(() => console.log(count.get())); |
Эти четыре блока позволяют построить полностью реактивный слой без громоздких switch-case
в редьюсерах.
Подключение MobX к React через mobx-react-lite
Для современных функций React достаточно установить два пакета:
npm i mobx mobx-react-lite
mobx-react-lite
экспортирует хук observer
, которым оборачиваем любой функциональный компонент. После этого все наблюдаемые свойства из стора будут автоматически пере-рендериваться.
import { observer } from "mobx-react-lite";
import { useLocalObservable } from "mobx-react-lite";
const Counter = observer(() => {
const store = useLocalObservable(() => ({
count: 0,
inc() {
this.count++;
},
}));
return (
<div>
<p>Текущее значение: {store.count}</p>
<button onClick={store.inc}>+1</button>
</div>
);
});
store.inc
объявлена внутри observable-объекта и автоматически превращается в действие (auto-action), если включена опция enforceActions: "observed"
.
Практический пример: список задач с автосохранением
// taskStore.ts
import { makeAutoObservable, reaction } from "mobx";
class TaskStore {
tasks: string[] = [];
constructor() {
makeAutoObservable(this);
reaction(
() => this.tasks.slice(),
(tasks) => localStorage.setItem("tasks", JSON.stringify(tasks))
);
}
add(task: string) {
this.tasks.push(task);
}
remove(index: number) {
this.tasks.splice(index, 1);
}
}
export const taskStore = new TaskStore();
// TaskList.tsx
import { observer } from "mobx-react-lite";
import { taskStore } from "./taskStore";
import { useState } from "react";
const TaskList = observer(() => {
const [input, setInput] = useState("");
const addTask = () => {
if (input.trim()) {
taskStore.add(input.trim());
setInput("");
}
};
return (
<div>
<h3>Мои задачи</h3>
<ul>
{taskStore.tasks.map((t, i) => (
<li key={i}>
{t}
<button onClick={() => taskStore.remove(i)}>✕</button>
</li>
))}
</ul>
<input
value={input}
onChange={(e) => setInput(e.target.value)}
placeholder="Новая задача"
/>
<button onClick={addTask}>Добавить</button>
</div>
);
});
export default TaskList;
reaction
заменяет привычный useEffect
: он срабатывает только при реальном изменении массива задач, а не при каждом рендере. При необходимости список можно виртуализовать, чтобы сохранялась отзывчивость.
Частые ошибки и антипаттерны
- Изменять observable напрямую без action приводит к непредсказуемым реакциям. Включите
enforceActions: "observed"
вmakeAutoObservable
или в глобальную конфигурацию MobX. - Объединять несколько стора в один огромный объект ухудшает читаемость и усложняет
autorun
. Разделяйте состояние на небольшие модули (например,uiStore
,dataStore
). - Частое использование
observer
каждыйobserver
создает подписку. Оборачивайте только те компоненты, которые действительно зависят от observable. - Не отписываться от
reaction
/autorun
в класс-компонентах и сервисах это обязательный шаг. - Смешивание MobX и Redux без четкого разграничения два подхода могут конфликтовать. Выбирайте один инструмент или используйте MobX только для локального UI-state.
FAQ / чеклист для быстрого старта
- Как проверить, что объект реактивен?
console.log(isObservable(store))
должно вернутьtrue
. - Можно ли использовать MobX без React? Да, MobX независим и часто применяется в Node-скриптах.
- Нужен ли
@observable
-декоратор? Нет,makeAutoObservable
обходит необходимость в декораторах, что удобно в CRA/Next.js. - Как отладить цепочку реакций? Включите
mobx.trace(true)
он выводит в консоль, какие наблюдатели сработали. - Стоит ли комбинировать с
useMemo
? Обычно нет, потому что MobX уже кеширует вычисления черезcomputed
.useMemo
имеет смысл только для тяжелых функций, не связанных с observable.
MobX удобен, когда нужно добавить реактивный слой в небольшие или средние проекты и синхронизировать состояние с внешними источниками (localStorage, websockets). При работе с React-производительностью такие техники, как мемоизация компонентов и оптимизация рендера, естественно дополняют MobX.