v19.2Latest

Обновление объектов в состоянии

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

Вы узнаете
  • Как правильно обновлять объект в состоянии React
  • Как обновить вложенный объект, не изменяя его напрямую
  • Что такое иммутабельность и как её не нарушить
  • Как сделать копирование объектов менее повторяющимся с помощью Immer

Что такое мутация?

В состоянии можно хранить значения любого типа JavaScript.

До сих пор вы работали с числами, строками и логическими значениями. Эти типы значений JavaScript являются «иммутабельными», то есть неизменяемыми или «только для чтения». Вы можете вызвать повторный рендер, чтобызаменитьзначение:

Состояниеxизменилось с0на5, носамо число0 не изменилось. В JavaScript невозможно внести изменения во встроенные примитивные значения, такие как числа, строки и логические значения.

Теперь рассмотрим объект в состоянии:

Технически возможно изменить содержимоесамого объекта.Это называется мутацией:

Однако, хотя объекты в состоянии React технически изменяемы, вам следует относиться к нимтак, как если быони были иммутабельными — как числа, логические значения и строки. Вместо их изменения вы всегда должны заменять их.

Рассматривайте состояние как доступное только для чтения

Другими словами, вы должнырассматривать любой объект JavaScript, помещённый в состояние, как доступный только для чтения.

В этом примере объект в состоянии представляет текущую позицию указателя. Красная точка должна перемещаться при касании или перемещении курсора над областью предпросмотра. Но точка остаётся в начальной позиции:

Проблема заключается в этом фрагменте кода.

Этот код изменяет объект, присвоенныйpositionизпредыдущего рендера.Но без использования функции установки состояния React не знает, что объект изменился. Поэтому React не предпринимает никаких действий в ответ. Это похоже на попытку изменить заказ после того, как вы уже съели блюдо. Хотя мутация состояния может работать в некоторых случаях, мы не рекомендуем её. Вы должны рассматривать значение состояния, доступное вам во время рендера, как доступное только для чтения.

Чтобы фактическивызвать повторный рендерв этом случае,создайтеновыйобъект и передайте его функции установки состояния:

С помощьюsetPositionвы сообщаете React:

  • Заменитьpositionэтим новым объектом
  • И снова отрендерить этот компонент

Обратите внимание, как красная точка теперь следует за вашим указателем при касании или наведении курсора на область предпросмотра:

Deep Dive
Локальная мутация — это нормально

Копирование объектов с помощью синтаксиса spread

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

Эти поля ввода не работают, потому что обработчикиonChangeмутируют состояние:

Например, эта строка мутирует состояние из прошлого рендера:

Надёжный способ получить желаемое поведение — создать новый объект и передать его вsetPerson. Но здесь вам также нужноскопировать существующие данные в него, потому что изменилось только одно из полей:

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

Теперь форма работает!

Обратите внимание, что вы не объявляли отдельную переменную состояния для каждого поля ввода. Для больших форм удобно хранить все данные сгруппированными в объекте — при условии, что вы обновляете его правильно!

Обратите внимание, что синтаксис spread...является «поверхностным» — он копирует данные только на один уровень вглубь. Это делает его быстрым, но также означает, что если вам нужно обновить вложенное свойство, вам придётся использовать его более одного раза.

Обновление вложенного объекта

Рассмотрим вложенную структуру объекта, подобную этой:

Если бы вы хотели обновитьperson.artwork.city, с мутацией это понятно, как сделать:

Но в React состояние считается неизменяемым! Чтобы изменитьcity, вам сначала нужно создать новый объектartwork(предварительно заполненный данными из предыдущего), а затем создать новый объектperson, который указывает на новыйartwork:

Или, записанный как один вызов функции:

Это получается немного многословно, но во многих случаях работает нормально:

Deep Dive
Объекты на самом деле не вложены

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

Если ваше состояние глубоко вложено, вы можете рассмотреть возможностьего уплощения.Но если вы не хотите менять структуру состояния, вы можете предпочесть сокращение для вложенных спредов.Immer— популярная библиотека, которая позволяет писать, используя удобный, но мутирующий синтаксис, и сама заботится о создании копий. С Immer код, который вы пишете, выглядит так, будто вы «нарушаете правила» и мутируете объект:

Но, в отличие от обычной мутации, она не перезаписывает предыдущее состояние!

Deep Dive
Как работает Immer?

Чтобы попробовать Immer:

  1. Выполнитеnpm install use-immer, чтобы добавить Immer в зависимости
  2. Затем заменитеimport { useState } from 'react'наimport { useImmer } from 'use-immer'

Вот приведённый выше пример, переписанный с использованием Immer:

Обратите внимание, насколько более лаконичными стали обработчики событий. Вы можете смешивать и сочетатьuseState и useImmerв одном компоненте сколько угодно. Immer — отличный способ сохранять обработчики обновления краткими, особенно если в вашем состоянии есть вложенность, а копирование объектов приводит к повторяющемуся коду.

Deep Dive
Почему мутирование состояния не рекомендуется в React?

Итоги

  • Рассматривайте всё состояние в React как неизменяемое.
  • Когда вы храните объекты в состоянии, их изменение не вызовет повторных рендеров и изменит состояние в «снимках» предыдущих рендеров.
  • Вместо изменения объекта создайте егоновуюверсию и запустите повторный рендер, установив состояние на неё.
  • Для создания копий объектов можно использовать синтаксис расширения объекта{...obj, something: 'newValue'}.
  • Синтаксис расширения поверхностный: он копирует только на один уровень вглубь.
  • Для обновления вложенного объекта необходимо создавать копии на всех уровнях, начиная с того, который вы обновляете.
  • Чтобы уменьшить количество повторяющегося кода для копирования, используйте Immer.

Try out some challenges

Challenge 1 of 3:Fix incorrect state updates #

This form has a few bugs. Click the button that increases the score a few times. Notice that it does not increase. Then edit the first name, and notice that the score has suddenly “caught up” with your changes. Finally, edit the last name, and notice that the score has disappeared completely.

Your task is to fix all of these bugs. As you fix them, explain why each of them happens.


Обновление объектов в состоянии | React Learn - Reflow Hub