v19.2Latest

Обновление массивов в состоянии

Массивы в JavaScript являются изменяемыми, но при хранении их в состоянии React следует относиться к ним как к неизменяемым. Как и в случае с объектами, когда вам нужно обновить массив, хранящийся в состоянии, вы должны создать новый массив (или сделать копию существующего), а затем установить состояние, используя этот новый массив.

Вы узнаете
  • Как добавлять, удалять или изменять элементы в массиве, хранящемся в состоянии React
  • Как обновить объект внутри массива
  • Как сделать копирование массивов менее повторяющимся с помощью Immer

Обновление массивов без мутации

В JavaScript массивы — это просто ещё один вид объекта.Как и с объектами,следует относиться к массивам в состоянии React как к доступным только для чтения.Это означает, что вам не следует переназначать элементы внутри массива, например, какarr[0] = 'bird', а также не следует использовать методы, изменяющие массив, такие какpush() и pop().

Вместо этого каждый раз, когда вам нужно обновить массив, вы должны передатьновыймассив в функцию установки состояния. Для этого вы можете создать новый массив из исходного массива в вашем состоянии, вызвав его неизменяющие методы, такие какfilter() и map(). Затем вы можете установить ваше состояние в получившийся новый массив.

Вот справочная таблица распространённых операций с массивами. При работе с массивами внутри состояния React вам следует избегать методов в левом столбце и отдавать предпочтение методам в правом столбце:

избегать (изменяет массив)предпочитать (возвращает новый массив)
добавлениеpush,unshiftconcat,[...arr]синтаксис spread (пример)
удалениеpop,shift,splicefilter,slice(пример)
заменаsplice,arr[i] = ...присваиваниеmap(пример)
сортировкаreverse,sortсначала скопируйте массив (пример)

В качестве альтернативы вы можетеиспользовать Immer, который позволяет использовать методы из обоих столбцов.

Подводный камень

К сожалению,slice и spliceимеют похожие названия, но сильно отличаются:

  • sliceпозволяет скопировать массив или его часть.
  • spliceизменяетмассив (для вставки или удаления элементов).

В React вы будете использоватьslice(безp!) гораздо чаще, потому что вы не хотите изменять объекты или массивы в состоянии.Обновление объектовобъясняет, что такое мутация и почему она не рекомендуется для состояния.

Добавление в массив

push()изменяет массив, чего вам следует избегать:

Вместо этого создайтеновыймассив, который содержит существующие элементыиновый элемент в конце. Есть несколько способов сделать это, но самый простой — использовать синтаксис...spread для массивов:

Теперь всё работает правильно:

Синтаксис spread для массивов также позволяет добавить элемент в начало, разместив егопередисходным...artists:

Таким образом, spread может выполнять работу какpush(), добавляя элемент в конец массива, так иunshift(), добавляя элемент в начало массива. Попробуйте это в песочнице выше!

Удаление из массива

Самый простой способ удалить элемент из массива —отфильтровать его. Другими словами, вы создадите новый массив, который не будет содержать этот элемент. Для этого используйте методfilter, например:

Нажмите кнопку «Delete» несколько раз и посмотрите на её обработчик клика.

Здесьartists.filter(a => a.id !== artist.id)означает «создать массив, состоящий из техartists, чьи ID отличаются отartist.id». Другими словами, кнопка «Delete» каждого художника отфильтруетэтогохудожника из массива, а затем запросит повторный рендер с полученным массивом. Обратите внимание, чтоfilterне изменяет исходный массив.

Преобразование массива

Если вы хотите изменить некоторые или все элементы массива, вы можете использоватьmap()для созданияновогомассива. Функция, которую вы передадите вmap, может решить, что делать с каждым элементом, основываясь на его данных или его индексе (или на обоих).

В этом примере массив содержит координаты двух кругов и квадрата. При нажатии кнопки он перемещает только круги вниз на 50 пикселей. Это делается путём создания нового массива данных с помощьюmap():

Замена элементов в массиве

Особенно часто возникает необходимость заменить один или несколько элементов в массиве. Присваивания видаarr[0] = 'bird'изменяют исходный массив, поэтому вместо этого вам также следует использоватьmap.

Чтобы заменить элемент, создайте новый массив с помощьюmap. Внутри вызоваmapвы получите индекс элемента в качестве второго аргумента. Используйте его, чтобы решить, вернуть исходный элемент (первый аргумент) или что-то ещё:

Вставка в массив

Иногда может потребоваться вставить элемент в определённую позицию, которая не является ни началом, ни концом. Для этого можно использовать синтаксис расширения массива...вместе с методомslice(). Методslice()позволяет вырезать «срез» массива. Чтобы вставить элемент, вы создадите массив, который включает срездоточки вставки, затем новый элемент, а затем остальную часть исходного массива.

В этом примере кнопка Insert всегда вставляет по индексу1:

Внесение других изменений в массив

Есть некоторые вещи, которые нельзя сделать только с помощью синтаксиса расширения и немутирующих методов, таких какmap() и filter(). Например, может потребоваться перевернуть или отсортировать массив. JavaScript-методыreverse() и sort()изменяют исходный массив, поэтому их нельзя использовать напрямую.

Однако можно сначала скопировать массив, а затем внести в него изменения.

Например:

Здесь используется синтаксис расширения[...list]для создания копии исходного массива. Теперь, имея копию, можно использовать мутирующие методы, такие какnextList.reverse()илиnextList.sort(), или даже присваивать отдельные элементы с помощьюnextList[0] = "something".

Однакодаже если вы скопируете массив, вы не можете напрямую изменять существующие элементывнутринего.Это связано с тем, что копирование является поверхностным — новый массив будет содержать те же элементы, что и исходный. Поэтому если вы изменяете объект внутри скопированного массива, вы изменяете существующее состояние. Например, такой код является проблемой.

ХотяnextList и list— это два разных массива,nextList[0] и list[0]указывают на один и тот же объект.Поэтому, изменяяnextList[0].seen, вы также изменяетеlist[0].seen. Это мутация состояния, которой следует избегать! Эту проблему можно решить аналогичнообновлению вложенных JavaScript-объектов— копируя отдельные элементы, которые нужно изменить, вместо их изменения. Вот как это делается.

Обновление объектов внутри массивов

Объекты не находятсяреально«внутри» массивов. В коде они могут казаться «внутри», но каждый объект в массиве — это отдельное значение, на которое массив «указывает». Вот почему нужно быть осторожным при изменении вложенных полей, таких какlist[0]. Список произведений искусства другого человека может указывать на тот же элемент массива!

При обновлении вложенного состояния необходимо создавать копии, начиная с точки, которую нужно обновить, и вплоть до верхнего уровня.Давайте посмотрим, как это работает.

В этом примере два отдельных списка произведений искусства имеют одинаковое начальное состояние. Они должны быть изолированы, но из-за мутации их состояние случайно стало общим, и установка флажка в одном списке влияет на другой список:

Проблема заключается в таком коде:

Хотя сам массивmyNextListявляется новым,сами элементыостаются теми же, что и в исходном массивеmyList. Поэтому изменениеartwork.seenизменяетисходныйэлемент artwork. Этот элемент artwork также находится вyourList, что и вызывает ошибку. Подобные ошибки бывает сложно осознать, но, к счастью, они исчезают, если избегать мутации состояния.

Вы можете использоватьmapдля замены старого элемента его обновлённой версией без мутации.

Здесь...— это синтаксис расширения объекта, используемый длясоздания копии объекта.

При таком подходе ни один из существующих элементов состояния не мутируется, и ошибка исправлена:

Как правило,вы должны мутировать только объекты, которые вы только что создали.Если бы вы добавлялиновыйartwork, вы могли бы его мутировать, но если вы работаете с чем-то, что уже находится в состоянии, вам нужно сделать копию.

Написание лаконичной логики обновления с помощью Immer

Обновление вложенных массивов без мутации может стать немного повторяющимся.Как и с объектами:

  • Как правило, вам не нужно обновлять состояние более чем на пару уровней в глубину. Если ваши объекты состояния очень глубокие, возможно, стоитпереструктурировать их по-другому, чтобы они были плоскими.
  • Если вы не хотите менять структуру состояния, вы можете предпочесть использоватьImmer, который позволяет писать с использованием удобного, но мутирующего синтаксиса и сам заботится о создании копий.

Вот пример Art Bucket List, переписанный с использованием Immer:

Обратите внимание, что с Immerмутация, такая какartwork.seen = nextSeen, теперь допустима:

Это потому, что вы не изменяетеисходноесостояние, а изменяете специальный объектdraft, предоставляемый Immer. Аналогично, вы можете применять мутирующие методы, такие какpush() и pop(), к содержимомуdraft.

Под капотом Immer всегда создаёт следующее состояние с нуля в соответствии с изменениями, которые вы внесли вdraft. Это позволяет сохранять ваши обработчики событий очень краткими, никогда не мутируя состояние.

Резюме

  • Вы можете помещать массивы в состояние, но не можете их изменять.
  • Вместо изменения массива создайте егоновуюверсию и обновите состояние на неё.
  • Вы можете использовать синтаксис расширения массива[...arr, newItem]для создания массивов с новыми элементами.
  • Вы можете использоватьfilter() и map()для создания новых массивов с отфильтрованными или преобразованными элементами.
  • Вы можете использовать Immer, чтобы сохранять ваш код кратким.

Try out some challenges

Challenge 1 of 4:Update an item in the shopping cart #

Fill in the handleIncreaseClick logic so that pressing ”+” increases the corresponding number: