Нужно ли мутировать массив товаров или создавать его копию для обновления состояния в React?
Что проверяют
Понимаете ли вы, по какому признаку React решает, что состояние изменилось. Ответ «копию» без объяснения засчитывают редко, спрашивают именно механизм: сравнение по ссылке, а не по содержимому.
Как отвечать
Копию. useState сравнивает новое значение со старым через Object.is. Если поменять массив на месте и отдать в сеттер ту же ссылку, React решит, что ничего не произошло, и перерисовки не будет. На том же поверхностном сравнении ссылок держатся React.memo и списки зависимостей useEffect и useMemo.
// ничего не перерисуется: ссылка на массив прежняя
items[i].qty += 1;
setItems(items);
// новая ссылка на массив и на изменённый элемент
setItems((prev) => prev.map((it, idx) =>
idx === i ? { ...it, qty: it.qty + 1 } : it
));Что стоит добавить:
- мутирующие методы
push,splice,sort,reverseзаменяются на спред,filter,map,toSorted; - копия поверхностная: вложенный объект копируется отдельно, иначе ссылка на него общая со старым состоянием;
- объект, который ещё не попал в состояние, мутировать можно, это дешевле и никого не обманывает;
- в Redux Toolkit код выглядит как мутация, но Immer под капотом собирает новое значение.
Где спотыкаются
Меняют элемент на месте и вызывают сеттер с тем же массивом. Экран не обновляется, и человек начинает «чинить» это лишним состоянием или принудительной перерисовкой вместо того, чтобы вернуть новую ссылку.
Копируют только верхний уровень: const next = [...items]; next[0].qty = 5;. Массив новый, а объект внутри тот же самый, поэтому дочерний компонент под React.memo останется со старой разметкой, и правка будет видна через раз.
Ставят key={index}. Пока список только дополняется с конца, это работает, но после вставки или удаления в середине React сопоставляет элементы неверно: введённый в поле текст и локальное состояние строк переезжают к соседям.