К чему приведет изменение старой коллекции элементов в MobX при получении новых данных
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Изменение старой коллекции в 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()
});
}
}
Практические рекомендации
- Всегда мутируйте observable-коллекции вместо замены ссылки
- Используйте
.replace()— наиболее читаемый способ полной замены содержимого - Оборачивайте асинхронные мутации в
runInActionили используйтеactionдекоратор - Структурируйте обновления так, чтобы добавлять/удалять/изменять только то, что реально изменилось — это минимизирует количество ререндеров
Итог
Изменение старой коллекции путём её мутации (splice, push, replace) сохраняет реактивность — все подписчики получат обновления. Замена ссылки на коллекцию (this.items = []) разрывает связь и приводит к тихим багам: UI перестаёт реагировать на изменения данных, что крайне сложно отлаживать в больших приложениях.