v19.2Latest

Реакция на ввод с помощью состояния

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

Вы узнаете
  • Чем декларативное программирование интерфейсов отличается от императивного
  • Как перечислить различные визуальные состояния вашего компонента
  • Как инициировать изменения между различными визуальными состояниями из кода

Сравнение декларативного и императивного подхода к UI

При проектировании взаимодействий с интерфейсом вы, вероятно, думаете о том, как интерфейсизменяетсяв ответ на действия пользователя. Рассмотрим форму, позволяющую пользователю отправить ответ:

  • Когда вы что-то вводите в форму, кнопка «Отправить»становится активной.
  • При нажатии «Отправить» и форма, и кнопкастановятся неактивными,и появляетсяиндикатор загрузки.
  • Если сетевой запрос успешен, формаскрывается,и появляется сообщение«Спасибо».
  • Если сетевой запрос завершился ошибкой, появляется сообщение об ошибке,и формасновастановится активной.

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

В машине, которой управляет человек с тревожным видом, представляющий JavaScript, пассажир приказывает водителю выполнить последовательность сложных поворотов пошаговой навигации.

Иллюстрация:Рэйчел Ли Нэйборс

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

В этом примере императивного программирования интерфейса форма построенабезиспользования React. Она использует только браузерныйDOM:

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

React был создан для решения этой проблемы.

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

В машине, которой управляет React, пассажир просит отвезти его в конкретное место на карте. React сам разбирается, как это сделать.

Иллюстрация:Рэйчел Ли Нэйборс

Декларативное мышление об интерфейсе

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

  1. Определитеразличные визуальные состояния вашего компонента
  2. Определитетриггеры для этих изменений состояния
  3. Представьтесостояние в памяти с помощьюuseState
  4. Удалитевсе несущественные переменные состояния
  5. Свяжитеобработчики событий для установки состояния

Шаг 1: Определите различные визуальные состояния вашего компонента

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

Сначала вам нужно визуализировать все различные «состояния» пользовательского интерфейса, которые может увидеть пользователь:

  • Пустое: Форма имеет отключенную кнопку «Отправить».
  • Ввод: Форма имеет активную кнопку «Отправить».
  • Отправка: Форма полностью отключена. Показывается индикатор загрузки.
  • Успех: Вместо формы показывается сообщение «Спасибо».
  • Ошибка: То же, что и состояние «Ввод», но с дополнительным сообщением об ошибке.

Как и дизайнер, вы захотите «создать макет» или «мокапы» для различных состояний, прежде чем добавлять логику. Например, вот макет только для визуальной части формы. Этот макет управляется пропом под названиемstatusсо значением по умолчанию'empty':

Вы можете назвать этот проп как угодно, именование не важно. Попробуйте изменитьstatus = 'empty'наstatus = 'success', чтобы увидеть появление сообщения об успехе. Создание макетов позволяет быстро итерировать над пользовательским интерфейсом, прежде чем вы подключите какую-либо логику. Вот более проработанный прототип того же компонента, всё ещё «управляемый» пропомstatus:

Шаг 2: Определите триггеры для этих изменений состояния

Вы можете инициировать обновления состояния в ответ на два вида входных данных:

  • Ввод пользователя,например, нажатие кнопки, ввод текста в поле, переход по ссылке.
  • Ввод компьютера,например, получение сетевого ответа, завершение таймаута, загрузка изображения.
Палец.Единицы и нули.

Иллюстрации отРэйчел Ли Наборс

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

  • Изменение текстового поля(человек) должно переключать его из состоянияПустов состояниеВводили обратно, в зависимости от того, пусто текстовое поле или нет.
  • Нажатие кнопки Отправить(человек) должно переключать его в состояниеОтправка.
  • Успешный сетевой ответ(компьютер) должен переключать его в состояниеУспех.
  • Неудачный сетевой ответ(компьютер) должен переключать его в состояниеОшибкас соответствующим сообщением об ошибке.
Примечание

Обратите внимание, что действия пользователя часто требуютобработчиков событий!

Чтобы визуализировать этот поток, попробуйте нарисовать каждое состояние на бумаге в виде помеченного круга, а каждое изменение между двумя состояниями — в виде стрелки. Таким образом можно набросать множество потоков и устранить ошибки задолго до реализации.

Блок-схема, движущаяся слева направо с 5 узлами. Первый узел с меткой 'empty' имеет одно ребро с меткой 'start typing', соединенное с узлом с меткой 'typing'. Этот узел имеет одно ребро с меткой 'press submit', соединенное с узлом с меткой 'submitting', у которого есть два ребра. Левое ребро имеет метку 'network error' и соединяется с узлом с меткой 'error'. Правое ребро имеет метку 'network success' и соединяется с узлом с меткой 'success'.Блок-схема, движущаяся слева направо с 5 узлами. Первый узел с меткой 'empty' имеет одно ребро с меткой 'start typing', соединенное с узлом с меткой 'typing'. Этот узел имеет одно ребро с меткой 'press submit', соединенное с узлом с меткой 'submitting', у которого есть два ребра. Левое ребро имеет метку 'network error' и соединяется с узлом с меткой 'error'. Правое ребро имеет метку 'network success' и соединяется с узлом с меткой 'success'.

Состояния формы

Шаг 3: Представление состояния в памяти с помощьюuseState

Далее вам нужно будет представить визуальные состояния вашего компонента в памяти с помощьюuseState.Ключевой момент — простота: каждый фрагмент состояния — это «движущаяся часть», ивам нужно как можно меньше «движущихся частей».Больше сложности ведет к большему количеству ошибок!

Начните с состояния, котороеобязательно должноприсутствовать. Например, вам нужно будет хранитьanswerдля ввода иerror(если он существует), чтобы сохранить последнюю ошибку:

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

Если вам сразу сложно придумать лучший способ, начните с добавления достаточного количества состояния, чтобы вы былиабсолютноуверены, что покрыты все возможные визуальные состояния:

Ваша первая идея, скорее всего, не будет лучшей, но это нормально — рефакторинг состояния является частью процесса!

Шаг 4: Удалите любые несущественные переменные состояния

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

Вот несколько вопросов, которые вы можете задать о своих переменных состояния:

  • Приводит ли это состояние к парадоксу?Например,isTyping и isSubmittingне могут одновременно бытьtrue. Парадокс обычно означает, что состояние недостаточно ограничено. Существует четыре возможные комбинации двух булевых значений, но только три соответствуют допустимым состояниям. Чтобы убрать «невозможное» состояние, можно объединить их вstatus, который должен быть одним из трёх значений:'typing','submitting'или'success'.
  • Доступна ли та же информация в другой переменной состояния?Ещё один парадокс:isEmpty и isTypingне могут бытьtrueодновременно. Разделив их на отдельные переменные состояния, вы рискуете, что они рассинхронизируются и вызовут ошибки. К счастью, можно убратьisEmptyи вместо этого проверятьanswer.length === 0.
  • Можно ли получить ту же информацию из обратного значения другой переменной состояния?isErrorне нужна, потому что можно проверитьerror !== null.

После этой чистки остаётся 3 (вместо 7!)существенныхпеременных состояния:

Вы знаете, что они существенные, потому что нельзя удалить ни одну из них, не нарушив функциональность.

Deep Dive
Устранение «невозможных» состояний с помощью редюсера

Шаг 5: Подключите обработчики событий для установки состояния

Наконец, создайте обработчики событий, которые обновляют состояние. Ниже представлена окончательная форма со всеми подключёнными обработчиками событий:

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

Итог

  • Декларативное программирование означает описание UI для каждого визуального состояния, а не микроуправление UI (императивный подход).
  • При разработке компонента:
    1. Определите все его визуальные состояния.
    2. Определите человеческие и компьютерные триггеры для изменений состояния.
    3. Смоделируйте состояние с помощьюuseState.
    4. Удалите несущественное состояние, чтобы избежать ошибок и парадоксов.
    5. Подключите обработчики событий для установки состояния.

Try out some challenges

Challenge 1 of 3:Add and remove a CSS class #

Make it so that clicking on the picture removes the background--active CSS class from the outer <div>, but adds the picture--active class to the <img>. Clicking the background again should restore the original CSS classes.

Visually, you should expect that clicking on the picture removes the purple background and highlights the picture border. Clicking outside the picture highlights the background, but removes the picture border highlight.