Состояние как снимок
Переменные состояния могут выглядеть как обычные переменные JavaScript, которые можно читать и записывать. Однако состояние ведёт себя скорее как снимок. Его установка не изменяет уже имеющуюся переменную состояния, а вместо этого запускает повторный рендер.
Вы узнаете
- Как установка состояния запускает повторные рендеры
- Когда и как обновляется состояние
- Почему состояние не обновляется сразу после его установки
- Как обработчики событий получают доступ к «снимку» состояния
Установка состояния запускает рендеры
Вы можете думать, что ваш пользовательский интерфейс изменяется напрямую в ответ на событие пользователя, например, клик. В React это работает немного иначе, чем в этой ментальной модели. На предыдущей странице вы видели, чтоустановка состояния запрашивает повторный рендерот React. Это означает, что для реакции интерфейса на событие вам необходимообновить состояние.
В этом примере, когда вы нажимаете «отправить»,setIsSent(true)говорит React перерендерить UI:
Вот что происходит при нажатии на кнопку:
- Выполняется обработчик события
onSubmit. setIsSent(true)устанавливаетisSentвtrueи ставит в очередь новый рендер.- React перерендеривает компонент в соответствии с новым значением
isSent.
Давайте подробнее рассмотрим взаимосвязь между состоянием и рендерингом.
Рендеринг делает снимок во времени
«Рендеринг»означает, что React вызывает ваш компонент, который является функцией. JSX, который вы возвращаете из этой функции, похож на снимок UI в определённый момент времени. Его пропсы, обработчики событий и локальные переменные были вычисленыс использованием его состояния на момент рендера.
В отличие от фотографии или кадра фильма, возвращаемый вами «снимок» UI является интерактивным. Он включает логику, такую как обработчики событий, которые определяют, что происходит в ответ на ввод данных. React обновляет экран в соответствии с этим снимком и подключает обработчики событий. В результате нажатие кнопки вызовет обработчик клика из вашего JSX.
Когда React перерендеривает компонент:
- React снова вызывает вашу функцию.
- Ваша функция возвращает новый снимок JSX.
- Затем React обновляет экран, чтобы он соответствовал снимку, возвращённому вашей функцией.
React выполняет функцию
Вычисление снимка
Обновление DOM-дерева
Иллюстрации отRachel Lee Nabors
Как память компонента, состояние — это не обычная переменная, которая исчезает после возврата из функции. Состояние фактически «живёт» в самом React — как будто на полке! — вне вашей функции. Когда React вызывает ваш компонент, он даёт вам снимок состояния для этого конкретного рендера. Ваш компонент возвращает снимок UI с новым набором пропсов и обработчиков событий в своём JSX, всё вычисленноес использованием значений состояния из этого рендера!
Вы говорите React обновить состояние
React обновляет значение состояния
React передаёт снимок значения состояния в компонент
Иллюстрации отRachel Lee Nabors
Вот небольшой эксперимент, чтобы показать, как это работает. В этом примере вы можете ожидать, что нажатие кнопки «+3» увеличит счётчик три раза, потому что она вызываетsetNumber(number + 1)три раза.
Посмотрите, что происходит при нажатии кнопки «+3»:
Обратите внимание, чтоnumberувеличивается только на единицу за клик!
Установка состояния изменяет его только дляследующегорендера.Во время первого рендераnumberбыл равен0. Вот почему в обработчикетого рендераonClickзначениеnumberвсё ещё равно0даже после вызоваsetNumber(number + 1):
Вот что обработчик клика этой кнопки говорит React сделать:
setNumber(number + 1):numberравно0, поэтомуsetNumber(0 + 1).- React готовится изменить
numberна1при следующем рендере.
- React готовится изменить
setNumber(number + 1):numberравно0, поэтомуsetNumber(0 + 1).- React готовится изменить
numberна1при следующем рендере.
- React готовится изменить
setNumber(number + 1):numberравно0, поэтомуsetNumber(0 + 1).- React готовится изменить
numberна1при следующем рендере.
- React готовится изменить
Несмотря на то, что вы вызвалиsetNumber(number + 1)три раза, в обработчике событийэтого рендера numberвсегда равен0, поэтому вы устанавливаете состояние в1три раза. Вот почему после завершения обработчика событий React повторно рендерит компонент сnumber, равным1, а не3.
Вы также можете визуализировать это, мысленно подставив значения переменных состояния в ваш код. Поскольку переменная состоянияnumber равна 0дляэтого рендера, её обработчик событий выглядит так:
Для следующего рендераnumberравен1, поэтомуобработчик клика для этого рендеравыглядит так:
Вот почему повторное нажатие на кнопку установит счётчик в2, затем в3при следующем клике и так далее.
Состояние во времени
Что ж, это было интересно. Попробуйте угадать, что выведет alert при нажатии на эту кнопку:
Если использовать метод подстановки из предыдущего раздела, можно догадаться, что в alert будет показано «0»:
Но что, если поставить таймер на alert, чтобы он сработалпослеповторного рендера компонента? Будет ли там «0» или «5»? Попробуйте угадать!
Удивлены? Если использовать метод подстановки, можно увидеть «снимок» состояния, переданный в alert.
Состояние, хранящееся в React, могло измениться к моменту запуска alert, но он был запланирован с использованием снимка состояния на момент взаимодействия пользователя!
Значение переменной состояния никогда не меняется в пределах одного рендера,даже если код обработчика события асинхронный. Внутриэтого рендераonClickзначениеnumberостаётся равным0даже после вызоваsetNumber(number + 5). Его значение было «зафиксировано», когда React «сделал снимок» UI, вызвав ваш компонент.
Вот пример того, как это делает ваши обработчики событий менее подверженными ошибкам синхронизации. Ниже приведена форма, которая отправляет сообщение с задержкой в пять секунд. Представьте себе такой сценарий:
- Вы нажимаете кнопку «Отправить», отправляя «Привет» Алисе.
- До истечения пятисекундной задержки вы меняете значение поля «Кому» на «Боб».
Что, по вашему мнению, отобразитalert? Будет ли это «Вы сказали Привет Алисе»? Или «Вы сказали Привет Бобу»? Сделайте предположение на основе своих знаний, а затем попробуйте:
React сохраняет значения состояния «зафиксированными» в пределах обработчиков событий одного рендера.Вам не нужно беспокоиться о том, изменилось ли состояние во время выполнения кода.
Но что, если вы хотите прочитать последнее состояние перед повторным рендером? Для этого вам понадобитсяфункция обновления состояния, о которой рассказывается на следующей странице!
Итоги
- Установка состояния запрашивает новый рендер.
- React хранит состояние вне вашего компонента, как будто на полке.
- Когда вы вызываете
useState, React предоставляет вам снимок состояниядля этого рендера. - Переменные и обработчики событий не «переживают» повторные рендеры. Каждый рендер имеет свои собственные обработчики событий.
- Каждый рендер (и функции внутри него) всегда будет «видеть» снимок состояния, который React предоставилэтомурендеру.
- Вы можете мысленно подставлять состояние в обработчики событий, аналогично тому, как вы думаете о отрендеренном JSX.
- Обработчики событий, созданные в прошлом, имеют значения состояния из рендера, в котором они были созданы.
Попробуйте выполнить несколько заданий
Challenge 1 of 1:Реализуйте светофор #
Вот компонент светофора для пешеходного перехода, который переключается при нажатии кнопки:
Добавьте alert в обработчик клика. Когда горит зелёный и написано «Идите», нажатие кнопки должно выводить «Следующий сигнал — стоп». Когда горит красный и написано «Стойте», нажатие кнопки должно выводить «Следующий сигнал — идите».
Имеет ли значение, разместите ли вы alert до или после вызова setWalk?
