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

К чему приведет изменение старой коллекции элементов в MobX при получении новых данных

1.0 Junior🔥 161 комментариев
#State Management

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

🐱
claude-haiku-4.5PrepBro AI20 мар. 2026 г.(ред.)

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

Изменение старой коллекции в MobX при получении новых данных

Этот вопрос касается одного из тонких, но важных аспектов работы с реактивными коллекциями в MobX. Ответ зависит от того, как именно вы обновляете коллекцию — и именно здесь кроется принципиальная разница.

Два подхода и их последствия

Подход 1: Замена всей коллекции (неправильно)

class Store {
  @observable items = [];

  fetchItems(newData) {
    this.items = newData; // полная замена
  }
}

При таком подходе:

  • MobX создаёт новый observable массив взамен старого
  • Все компоненты и computed-значения, которые держали ссылку на старый массив, теряют реактивность
  • Они по-прежнему смотрят на старый объект, который больше не отслеживается
  • Это приводит к "зависшим" компонентам — UI не обновляется даже при изменении данных

Подход 2: Мутация существующей коллекции (правильно)

class Store {
  @observable items = [];

  fetchItems(newData) {
    // Вариант A — через splice
    this.items.splice(0, this.items.length, ...newData);

    // Вариант B — через метод replace (MobX 4/5)
    this.items.replace(newData);

    // Вариант C — через runInAction
    runInAction(() => {
      this.items.length = 0;
      this.items.push(...newData);
    });
  }
}

При мутации существующего массива:

  • MobX отслеживает изменения внутри observable-объекта
  • Все подписчики, держащие ссылку на исходный массив, автоматически получают уведомления
  • UI реагирует корректно

Почему это происходит — механизм MobX

MobX использует прокси-объекты (или defineProperty в старых версиях) для отслеживания доступа и изменений. Когда компонент читает store.items, он подписывается на конкретный прокси-объект, который представляет этот массив.

Если вы делаете this.items = newData — вы меняете ссылку в store. Старый прокси остаётся нетронутым. Компонент, подписанный на старый прокси, ничего не замечает — для него "его" observable не изменился.

Если вы мутируете коллекцию — прокси остаётся тем же, но его внутреннее содержимое меняется, и MobX уведомляет всех подписчиков.

Паттерн ObservableMap

Аналогичная ситуация с ObservableMap:

@observable itemsMap = new Map();

// Плохо:
this.itemsMap = new Map(newData); // потеря реактивности

// Хорошо:
this.itemsMap.clear();
newData.forEach(([k, v]) => this.itemsMap.set(k, v));

MobX 6 и makeAutoObservable

В MobX 6 с makeAutoObservable поведение то же самое. Однако появилась возможность использовать observable.array явно:

import { observable, runInAction } from "mobx";

class Store {
  items = [];

  constructor() {
    makeAutoObservable(this);
  }

  async loadItems() {
    const data = await api.getItems();
    runInAction(() => {
      // items всё ещё тот же observable-массив, просто обновлённый
      this.items.replace(data); // MobX предоставляет .replace()
    });
  }
}

Практические рекомендации

  1. Всегда мутируйте observable-коллекции вместо замены ссылки
  2. Используйте .replace() — наиболее читаемый способ полной замены содержимого
  3. Оборачивайте асинхронные мутации в runInAction или используйте action декоратор
  4. Структурируйте обновления так, чтобы добавлять/удалять/изменять только то, что реально изменилось — это минимизирует количество ререндеров

Итог

Изменение старой коллекции путём её мутации (splice, push, replace) сохраняет реактивность — все подписчики получат обновления. Замена ссылки на коллекцию (this.items = []) разрывает связь и приводит к тихим багам: UI перестаёт реагировать на изменения данных, что крайне сложно отлаживать в больших приложениях.