v19.2Latest

Очередь обновлений состояния

Установка переменной состояния ставит в очередь новый рендер. Но иногда вам может потребоваться выполнить несколько операций со значением перед постановкой следующего рендера в очередь. Для этого полезно понимать, как React группирует обновления состояния.

Вы узнаете
  • Что такое «группировка» и как React использует её для обработки нескольких обновлений состояния
  • Как применить несколько обновлений к одной и той же переменной состояния подряд

React группирует обновления состояния

Вы можете ожидать, что нажатие кнопки «+3» увеличит счётчик три раза, потому что она вызываетsetNumber(number + 1)три раза:

Однако, как вы могли вспомнить из предыдущего раздела,значения состояния каждого рендера фиксированы, поэтому значениеnumberвнутри обработчика событий первого рендера всегда равно0, независимо от того, сколько раз вы вызываетеsetNumber(1):

Но здесь играет роль ещё один фактор.React ждёт, покавеськод в обработчиках событий не выполнится, прежде чем обрабатывать ваши обновления состояния.Вот почему повторный рендер происходит толькопослевсех этих вызововsetNumber().

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

Элегантный курсор в ресторане несколько раз делает заказ с помощью React, играющего роль официанта. После того как она несколько раз вызывает setState(), официант записывает последний запрошенный ею вариант как её окончательный заказ.

Иллюстрация:Rachel Lee Nabors

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

React не группирует обновления междунесколькиминамеренными событиями, такими как клики— каждый клик обрабатывается отдельно. Будьте уверены, React выполняет группировку только тогда, когда это в целом безопасно. Это гарантирует, что, например, если первый клик по кнопке отключает форму, второй клик не отправит её снова.

Обновление одного и того же состояния несколько раз до следующего рендера

Это нечастая ситуация, но если вы хотите обновить одну и ту же переменную состояния несколько раз до следующего рендера, вместо передачиследующего значения состояния, напримерsetNumber(number + 1), вы можете передатьфункцию, которая вычисляет следующее состояние на основе предыдущего в очереди, напримерsetNumber(n => n + 1). Это способ сказать React «сделать что-то со значением состояния», а не просто заменить его.

Попробуйте увеличить счётчик сейчас:

Здесьn => n + 1называетсяфункцией-обновителем.Когда вы передаёте её установщику состояния:

  1. React ставит эту функцию в очередь для обработки после выполнения всего остального кода в обработчике событий.
  2. Во время следующего рендера React проходит по очереди и выдаёт вам окончательное обновлённое состояние.

Вот как React обрабатывает эти строки кода во время выполнения обработчика событий:

  1. setNumber(n => n + 1):n => n + 1— это функция. React добавляет её в очередь.
  2. setNumber(n => n + 1):n => n + 1— это функция. React добавляет её в очередь.
  3. setNumber(n => n + 1):n => n + 1— это функция. React добавляет её в очередь.

Когда вы вызываетеuseStateво время следующего рендера, React проходит по очереди. Предыдущее состояниеnumberбыло0, поэтому React передаёт это значение первой функции-обновителю в качестве аргументаn. Затем React берёт возвращаемое значение вашей предыдущей функции-обновителя и передаёт его следующему обновителю в качествеnи так далее:

обновление в очередиnвозвращает
n => n + 100 + 1 = 1
n => n + 111 + 1 = 2
n => n + 122 + 1 = 3

React сохраняет3как окончательный результат и возвращает его изuseState.

Вот почему нажатие «+3» в приведённом выше примере корректно увеличивает значение на 3.

Что произойдёт, если вы обновите состояние после его замены

А что насчёт этого обработчика событий? Как вы думаете, каким будетnumberпри следующем рендере?

Вот что этот обработчик событий говорит React сделать:

  1. setNumber(number + 5):numberравно0, поэтомуsetNumber(0 + 5). React добавляет«заменить на5»в свою очередь.
  2. setNumber(n => n + 1):n => n + 1— это функция-обновитель. React добавляетэту функциюв свою очередь.

Во время следующего рендера React проходит по очереди состояний:

поставленное в очередь обновлениеnвозвращает
”заменить на50(не используется)5
n => n + 155 + 1 = 6

React сохраняет6как окончательный результат и возвращает его изuseState.

Примечание

Вы могли заметить, чтоsetState(5)фактически работает какsetState(n => 5), ноnне используется!

Что произойдёт, если заменить состояние после его обновления

Попробуем ещё один пример. Как вы думаете, каким будетnumberпри следующем рендере?

Вот как React обрабатывает эти строки кода при выполнении этого обработчика события:

  1. setNumber(number + 5):numberравно0, поэтомуsetNumber(0 + 5). React добавляет“заменить на5в свою очередь.
  2. setNumber(n => n + 1):n => n + 1— это функция-обновитель. React добавляетэту функциюв свою очередь.
  3. setNumber(42): React добавляет“заменить на42в свою очередь.

Во время следующего рендера React проходит по очереди состояния:

поставленное в очередь обновлениеnвозвращает
”заменить на50(не используется)5
n => n + 155 + 1 = 6
”заменить на426(не используется)42

Затем React сохраняет42как окончательный результат и возвращает его изuseState.

Подводя итог, вот как можно думать о том, что вы передаёте в сеттер состоянияsetNumber:

  • Функция-обновитель(например,n => n + 1) добавляется в очередь.
  • Любое другое значение(например, число5) добавляет “заменить на5” в очередь, игнорируя уже поставленные в очередь обновления.

После завершения обработчика события React запустит повторный рендер. Во время повторного рендера React обработает очередь. Функции-обновители выполняются во время рендеринга, поэтомуфункции-обновители должны бытьчистымии тольковозвращатьрезультат. Не пытайтесь устанавливать состояние изнутри них или выполнять другие побочные эффекты. В Строгом режиме React запустит каждую функцию-обновитель дважды (но отбросит второй результат), чтобы помочь вам найти ошибки.

Соглашения об именовании

Обычно аргумент функции-обновителя называют по первым буквам соответствующей переменной состояния:

Если вы предпочитаете более многословный код, другое распространённое соглашение — повторять полное имя переменной состояния, напримерsetEnabled(enabled => !enabled), или использовать префикс, напримерsetEnabled(prevEnabled => !prevEnabled).

Итоги

  • Установка состояния не изменяет переменную в текущем рендере, а запрашивает новый рендер.
  • React обрабатывает обновления состояния после завершения работы обработчиков событий. Это называется батчингом.
  • Чтобы обновить некоторое состояние несколько раз в одном событии, вы можете использовать функцию-обновительsetNumber(n => n + 1).

Попробуйте выполнить несколько заданий

Challenge 1 of 2:Исправьте счётчик запросов #

Вы работаете над приложением для арт-маркетплейса, которое позволяет пользователю отправлять несколько заказов на один товар одновременно. Каждый раз, когда пользователь нажимает кнопку «Купить», счётчик «В ожидании» должен увеличиваться на единицу. Через три секунды счётчик «В ожидании» должен уменьшиться, а счётчик «Завершено» — увеличиться.

Однако счётчик «В ожидании» работает не так, как задумано. Когда вы нажимаете «Купить», он уменьшается до -1 (чего быть не должно!). И если вы быстро нажмёте дважды, оба счётчика начинают вести себя непредсказуемо.

Почему это происходит? Исправьте оба счётчика.