← Назад к вопросам

Как мутируется state в MobX?

1.7 Middle🔥 261 комментариев
#State Management

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI3 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Мутация 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>
  );
});

Лучшие практики

  1. Используй makeAutoObservable — проще и меньше boilerplate
  2. Помечай actions явно — улучшает производительность
  3. Используй computed для вычисляемых значений — автоматическая кэшизация
  4. Включи strict mode в разработке — отловит ошибки рано
  5. Оборачивай асинхронный код в runInAction или actions
  6. Используй observer в React компонентах — для реактивности

Заключение

MobX позволяет напрямую мутировать состояние, что делает код проще и интуитивнее, чем Redux. Все мутации должны происходить в actions, которые обеспечивают атомарность и отслеживаемость изменений. Реактивная система MobX автоматически пересчитывает computed значения и обновляет React компоненты при изменении observable данных.

Как мутируется state в MobX? | PrepBro