Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Мутация State в MobX
MobX — это библиотека управления состоянием, которая использует реактивное программирование. В отличие от Redux, где состояние неизменяемо (immutable), MobX позволяет напрямую мутировать состояние, что часто упрощает работу с ним.
Основной принцип: прямая мутация
MobX позволяет напрямую изменять свойства объекта состояния, без создания новых объектов.
import { makeObservable, observable } from "mobx";
class Store {
count = 0; // Observable состояние
constructor() {
makeObservable(this, {
count: observable, // Отметить как observable
increment: true // Отметить метод
});
}
increment() {
this.count++; // Прямая мутация!
}
}
const store = new Store();
store.increment(); // count = 1
store.increment(); // count = 2
Ключевые концепции
1. Observable — отслеживаемое состояние
import { observable } from "mobx";
// Способ 1: Через makeObservable
class User {
name = "John";
age = 30;
constructor() {
makeObservable(this, {
name: observable,
age: observable,
setBirthYear: true
});
}
setBirthYear(year) {
this.age = new Date().getFullYear() - year;
}
}
// Способ 2: Через observable()
const user = observable({
name: "John",
age: 30,
setBirthYear(year) {
this.age = new Date().getFullYear() - year;
}
});
// Способ 3: Автоматическая обработка с makeAutoObservable
class Store {
name = "John";
constructor() {
// Все поля и методы автоматически observable
makeAutoObservable(this);
}
setName(name) {
this.name = name; // Мутация отслеживается
}
}
2. Действия (Actions) — группировка мутаций
Actions оборачивают несколько мутаций и выполняют их атомарно (все вместе или ничего).
class TodoStore {
todos = [];
constructor() {
makeObservable(this, {
todos: observable,
addTodo: true, // Это action
toggleTodo: true,
removeTodo: true
});
}
// Action: группирует все изменения
addTodo(title) {
// Все мутации здесь выполняются атомарно
const id = this.todos.length + 1;
this.todos.push({
id,
title,
completed: false
});
}
toggleTodo(id) {
const todo = this.todos.find(t => t.id === id);
if (todo) {
todo.completed = !todo.completed; // Прямая мутация
}
}
removeTodo(id) {
this.todos = this.todos.filter(t => t.id !== id); // Или переassign
}
}
const store = new TodoStore();
store.addTodo("Learn MobX"); // Все мутации произойдут вместе
3. Computed — вычисляемые значения
Вычисляемые значения автоматически пересчитываются при изменении зависимостей.
import { computed } from "mobx";
class Store {
todos = [];
constructor() {
makeObservable(this, {
todos: observable,
completedCount: computed,
incompleteCount: computed
});
}
// Computed: автоматически пересчитывается при изменении todos
get completedCount() {
return this.todos.filter(t => t.completed).length;
}
get incompleteCount() {
return this.todos.filter(t => !t.completed).length;
}
addTodo(title) {
this.todos.push({ title, completed: false });
// completedCount и incompleteCount автоматически пересчитаются
}
}
4. Reactions — реакции на изменения
Выполняют побочные эффекты при изменении состояния.
import { reaction } from "mobx";
class Store {
user = null;
constructor() {
makeAutoObservable(this);
// Reaction: следит за user.id и вызывает функцию при изменении
reaction(
() => this.user?.id, // Что отслеживать
(userId) => { // Что делать при изменении
if (userId) {
console.log("User changed, fetching data...");
this.fetchUserData(userId);
}
}
);
}
setUser(user) {
this.user = user; // Мутация, reaction сработает
}
async fetchUserData(userId) {
const data = await fetch(`/api/users/${userId}`);
// ...
}
}
Практический пример в React
// store.js
import { makeAutoObservable } from "mobx";
class CounterStore {
count = 0;
constructor() {
makeAutoObservable(this);
}
increment() {
this.count++;
}
decrement() {
this.count--;
}
reset() {
this.count = 0;
}
get isPositive() {
return this.count > 0;
}
}
export const counterStore = new CounterStore();
// React компонент
import { observer } from "mobx-react-lite";
import { counterStore } from "./store";
export const Counter = observer(() => {
return (
<div>
<h1>Count: {counterStore.count}</h1>
<p>Is positive? {counterStore.isPositive ? "Yes" : "No"}</p>
<button onClick={() => counterStore.increment()}>+</button>
<button onClick={() => counterStore.decrement()}>-</button>
<button onClick={() => counterStore.reset()}>Reset</button>
</div>
);
});
Сравнение: Redux vs MobX
// REDUX: Immutable, Reducer pattern
const initialState = { count: 0 };
function counterReducer(state = initialState, action) {
switch (action.type) {
case "INCREMENT":
return { ...state, count: state.count + 1 }; // Новый объект!
case "DECREMENT":
return { ...state, count: state.count - 1 };
default:
return state;
}
}
// MOBX: Mutable, Direct mutation
class CounterStore {
count = 0;
constructor() {
makeAutoObservable(this);
}
increment() {
this.count++; // Прямая мутация!
}
decrement() {
this.count--;
}
}
Правила мутации в MobX
1. Мутируй в Actions
class Store {
count = 0;
constructor() {
makeObservable(this, {
count: observable,
increment: true // Это action
});
}
// Правильно: мутация в action
increment() {
this.count++;
}
}
// Неправильно: мутация вне action (в strict mode будет ошибка)
// store.count++; // Ошибка если strict mode включен!
2. Strict Mode
import { configure } from "mobx";
// Строгий режим: все мутации должны быть в actions
configure({
enforceActions: "always" // или "never", "observed"
});
class Store {
value = 0;
constructor() {
makeAutoObservable(this);
}
setValue(val) {
this.value = val; // OK в action
}
}
// Будет ошибка:
// store.value = 10; // Error: Mutation outside action
3. Асинхронные мутации
class UserStore {
user = null;
loading = false;
error = null;
constructor() {
makeAutoObservable(this);
}
// Action для асинхронного кода
async fetchUser(id) {
this.loading = true;
this.error = null;
try {
const response = await fetch(`/api/users/${id}`);
const user = await response.json();
// Мутация в resolve части
runInAction(() => {
this.user = user;
this.loading = false;
});
} catch (err) {
runInAction(() => {
this.error = err.message;
this.loading = false;
});
}
}
}
Или с async/await:
// Способ 1: await в action
class UserStore {
constructor() {
makeObservable(this, {
fetchUser: true // Помечаем как action
});
}
async fetchUser(id) {
this.loading = true;
try {
const response = await fetch(`/api/users/${id}`);
this.user = await response.json();
} finally {
this.loading = false;
}
}
}
// Способ 2: Wrap в runInAction
import { runInAction } from "mobx";
async function someAsyncFunction() {
const data = await fetchData();
// Мутация внутри runInAction
runInAction(() => {
store.data = data;
});
}
Практический пример: Корзина покупок
class CartStore {
items = [];
constructor() {
makeAutoObservable(this);
}
// Добавить товар
addItem(product) {
const existingItem = this.items.find(i => i.id === product.id);
if (existingItem) {
existingItem.quantity++; // Мутация существующего товара
} else {
this.items.push({ ...product, quantity: 1 }); // Мутация массива
}
}
// Удалить товар
removeItem(id) {
const index = this.items.findIndex(i => i.id === id);
if (index !== -1) {
this.items.splice(index, 1); // Мутация массива
}
}
// Вычисляемое: общая стоимость
get total() {
return this.items.reduce((sum, item) => sum + item.price * item.quantity, 0);
}
// Вычисляемое: количество товаров
get itemCount() {
return this.items.reduce((count, item) => count + item.quantity, 0);
}
// Очистить корзину
clear() {
this.items = []; // Переassign массива
}
}
// В React компоненте
const CartSummary = observer(({ store }) => {
return (
<div>
<p>Items: {store.itemCount}</p>
<p>Total: ${store.total.toFixed(2)}</p>
<button onClick={() => store.clear()}>Clear Cart</button>
</div>
);
});
Лучшие практики
- Используй makeAutoObservable — проще и меньше boilerplate
- Помечай actions явно — улучшает производительность
- Используй computed для вычисляемых значений — автоматическая кэшизация
- Включи strict mode в разработке — отловит ошибки рано
- Оборачивай асинхронный код в runInAction или actions
- Используй observer в React компонентах — для реактивности
Заключение
MobX позволяет напрямую мутировать состояние, что делает код проще и интуитивнее, чем Redux. Все мутации должны происходить в actions, которые обеспечивают атомарность и отслеживаемость изменений. Реактивная система MobX автоматически пересчитывает computed значения и обновляет React компоненты при изменении observable данных.