В КУРСЕ?

Разбираемся в теме

Почему два обновления состояния React иногда прибавляют только единицу?

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

Текущий рендер видит свой снимок

При рендере React вызывает компонент и получает описание интерфейса. Локальные значения и обработчики связаны с состоянием этого рендера. Вызов функции обновления запрашивает следующее состояние, но не переписывает уже доступную переменную внутри выполняющегося обработчика. Поэтому чтение этой переменной сразу после вызова ещё относится к прежнему снимку. В нашем примере переменная count равна четырём. Выражение count + 1 в текущем обработчике вычисляется как пять. Если два раза передать результат этого выражения в setCount, оба раза передаётся одно и то же число. Это две просьбы установить пять, а не две независимые команды прибавить единицу к последнему результату очереди.

Отличайте готовое значение от функции обновления

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

Новый расчёт интерфейса не равен немедленному изменению экрана

React может группировать обновления из обработчика, чтобы обработать их после его выполнения. Это не означает объединения всех отдельных пользовательских нажатий в одно событие. Рассматриваемый пример относится к нескольким вызовам внутри одного обработчика. Если сравнивать его с двумя отдельными кликами, условия уже будут другими. После запроса обновления следует новый расчёт компонентов, а затем фиксация нужных изменений в интерфейсе. Сам по себе повторный рендер не требует менять каждый узел страницы: применяются необходимые различия. Поэтому при отладке полезно отдельно спрашивать, какое состояние было прочитано, что попало в очередь и какой результат должен получить следующий рендер. Такая последовательность точнее объяснения, что состояние просто меняется когда-нибудь позже.

Попробуйте на практике

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

  1. Запишите исходное состояние четыре. Составьте очередь из двух готовых значений, каждое вычислено как исходное число плюс один.
  2. Пройдите очередь последовательно, заменяя промежуточное состояние каждым готовым значением. Запишите итог и объясните, почему это не шесть.
  3. Начните снова с четырёх и составьте очередь из двух функций прибавления единицы. Передавайте результат первой во вторую.
  4. Сравните оба итога и подпишите условия примера: один обработчик, один исходный снимок и отсутствие других обновлений. Отдельно отметьте, что два разных клика требуют нового разбора.

Как проверить результат. Модель понята, если первая очередь даёт пять, вторая шесть, а чтение переменной текущего обработчика не перепутано с результатом следующего рендера.

Частые вопросы

Нужно ли всегда передавать функцию обновления?

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

Почему вывод в консоль сразу после обновления показывает старое число?

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

Самостоятельный разбор темы. Содержание конкретной обучающей программы здесь не представлено.

Зарегистрируйтесь, чтобы уточнить возможность доступа к этому материалу

Зарегистрироваться
← К списку материалов