Вашему вниманию предлагается принципиальная схема построения пользовательского интерфейса средствами библиотеки React:
В схеме два этапа:
- Пишем HTML-код.
- Вырезаем в нём дырки.
Что? Вырезаем дырки? Именно! Такие, чтобы было видно, что там находится сзади – вот так:
Получается вроде фотоплаката с красивой картинкой и отверстием, куда вы просовываете голову, чтобы сделать смешной снимок (по-умному такой стенд ещё называется тантамареской).
Конечно, мы не вырезаем «дырки» в коде физически – ножницами, канцелярским ножом или ещё каким-нибудь острым предметом. И в документации React вы такого термина не найдете. Это всего лишь ментальная модель, дающая понять, как всё работает. Особенно, если вы новичок в React.
Мастерами кунг-фу не рождаются
Начнём с самого простого React-приложения – выведем на экран Кунг-фу Панду в виде эмодзи:
Компоненты в React выглядят как обычные функции:
Всего лишь возвращаем из функции тег HTML с нужным контентом: обычная JS-функция, обычный return
, обычный HTML (есть некоторые тонкости, но в целом – да, самый обычный). В общем, пока что ничего сложного.
Добавляем динамику
Получившееся приложение не очень функционально – его и приложением-то не назвать. Добавим интерактив. Например, будем менять персонажа на Кунг-фу Обезьяну, Кунг-фу Тигрицу или, может быть... Кунг-фу Хрюшку? Ниже представлен интерактивный элемент – кнопки можно нажимать, и наблюдать изменение, а также смотреть HTML и JS-код (по кнопке Babel
).
Всё, что мы до сих пор писали в HTML, было как рисунок маркером на картоне – неизменяемо. Как нарисовали, так и осталось. Но что делать, если на этом рисунке нужно поменять пару символов – или подставить чью-то голову? Да просто вырезать дырку в нужном месте – вот так:
Теперь в это отверстие можно поместить кого угодно – хоть панду, хоть хрюшку:
Нужно лишь поменять значение переменной who
в коде.
Чтобы менять персонажа динамически, давайте добавим кнопку. Клик по ней будет изменять значение переменной.
Как нам это сделать? Возможно, вы думаете, что всё просто:
Но, к сожалению, в React это не сработает. Мы не можем просто так менять данные (и это, на самом деле защищает нас от многих проблем). React предоставляет специальный способ – локальное состояние компонента:
Этот код может напугать неподготовленного читателя, но в нем нет ничего сложного. Первая строчка эквивалента следующему фрагменту:
Метод React.useState()
возвращает массив из двух элементов. Первый – это само состояние компонента, а второй – метод для его изменения. Для краткости мы используем синтаксис деструктуризации массива – сразу записываем его элементы в отдельные переменные.
В метод можно передать начальное значение who
. Поставим туда Панду 🐼.
Теперь в обработчике клика по кнопке (атрибут onClick
) мы вызываем полученную функцию setWho
, чтобы изменить состояние на Тигрицу 🐯.
Другими словами, функция setWho
выбирает, кто именно будет «фотографироваться».
Отделяем данные от пользовательского интерфейса
Эта аналогия отлично демонстрирует важную идею React. Существует два понятия:
- Общая структура (которая практически всегда остается статической);
- Данные (которые время от времени изменяются).
Эти понятия должны работать отдельно друг от друга.
Когда мы хотим что-то изменить в React, то почти всегда обращаемся к данным. Мы не изменяем напрямую UI-элементы (узлы DOM). Вместо этого мы изменяем данные, а элементы интерфейса обновляются автоматически.
Если до этого вы использовали в работе только инструменты типа jQuery, такой подход может показаться непонятным. Действительно, с jQuery мы берем существующий в DOM элемент и вручную меняем в нем текст:
Но в React мы меняем только данные, а взаимодействие с DOM берет на себя библиотека:
В этом коде нигде не упоминается div
или другой DOM-элемент. HTML меняется автоматически, так как мы заранее объяснили React, что и где нужно подставить. Иначе говоря, мы прорезали в HTML отверстие, куда теперь подставляем то, что нам нужно.
Ментальная модель React
Итак, для работы с React мы должны понять несколько концепций:
- Мы пишем HTML, чтобы определить статическую структуру пользовательского интерфейса.
- Затем мы делаем в нем «дырки» с помощью конструкции
{ }
, чтобы в них можно было разместить динамические данные. - Изменяющиеся данные – это состояние компонента, и для них мы используем специальные методы React.
Самая важная идея заключается в том, что мы отделяем интерфейс (фотошаблон) от данных (лица в отверстии). В результате основную работу мы проводим именно с данными, не затрагивая интерфейс.
Домашнее задание
Для закрепления знаний попробуйте реализовать вот такое приложение:
Решение здесь.
Берёмся за Input
Чтобы лучше понять описанную ментальную модель React, обратимся к тегу input
.
Напишем простой компонент:
Input
по своей природе интерактивен – он позволяет пользователю вводить символы и сразу же отображает результат ввода.
Как получить значение?
Сначала добавим кнопку. При клике на нее будем показывать окошко с текстом из поля ввода:
Как получить этот текст из инпута? Если вы подумали, что нужно каким-то образом получить доступ к DOM-элементу, подумайте еще раз! Помните, что мы не должны взаимодействовать с интерфейсом – только с данными!
Вспомните про наш фотоплакат: нужно в статической структуре прорезать дырку { }
– но где? В том месте, где находятся изменяющиеся данные – в атрибуте value
:
Теперь текст хранится в переменной draft
, и мы легко можем его вывести:
Вопрос вида «какой текст находится в поле ввода?» – неправильный. Мы должны спросить: «какие данные привязаны к этому инпуту?» Или «что находится за этой дыркой?»
Отверстия можно прорезать в контенте тега и в значениях атрибутов. Но не стоит делать это в других местах.
Как изменить значение?
Добавим другую кнопку, клик на которую должен изменять текст в поле ввода. Как изменить значение инпута?
Даже не пытайтесь вновь думать о прямом доступе к DOM элементу!
Помните – мы работаем с данными.
Уже подготовлено место в значении атрибута value
, остается лишь изменить переменную draft
– и инпут обновится автоматически. Как это сделать, мы уже знаем.
Можем сделать что-то в духе следующего интерактивного примера:
Как починить пользовательский ввод?
Кнопки работают отлично, но если вы попробуете что-то напечатать в поле ввода, то ничего не выйдет! Попробуйте, если еще не сделали этого. Как нам удалось сломать обычный инпут?
Посмотрим на код ещё раз:
Значение инпута чётко определено переменной draft
, которая выглядывает из прорезанной нами дырки. Но мы не меняем эту переменную – поэтому и текст в поле не меняется.
Единственный способ изменить его – воспользоваться функцией setDraft
. Сейчас мы вызываем её только при клике на кнопку – в атрибуте onClick
. И ничего не делаем при вводе символов в инпут. Исправим это досадное недоразумение:
Теперь мы отслеживаем событие change
– в React
оно происходит при любом изменении инпута, а не только после потери им фокуса, это больше похоже на JavaScript событие input
.
В качестве первого аргумента обработчик получает объект события (это не совсем привычный нам Event
, но все нужные методы и свойства у него есть). Так что мы можем достать значение инпута из event.target.value
и обновить состояние компонента.
Контролируем инпут
Итак, элемент input
отлично демонстрирует концепцию «данные отдельно от интерфейса».
- Мы «привязываем» некоторый текст к инпуту вместо того, чтобы «извлекать» его из DOM-элемента.
- Чтобы обновить значение инпута, нужно обновить привязанные к нему данные.
- Для хранения и автоматического обновления текста используем состояние React компонента.
- Чтобы отслеживать пользовательский ввод, устанавливаем обработчик на событие
onChange
.
Такой инпут называется контролируемым – ведь от самого элемента тут ничего не зависит, только мы определяем, что в нем будет находиться.
Точно так же мы можем установить полный контроль над textarea
, select
или другим интерактивным тегом.
Контролируемые компоненты – это не единственно возможный подход для работы с элементами форм (React позволяет работать с DOM узлами напрямую), но в большинстве случаев – это желательный подход.
Мастерами кунг-фу становятся!
Домашнее задание: Кунг-фу Панда наносит удар.
Создайте приложение, как в следующем интерактивном примере (потом по кнопке Babel
можете сравнить свой результат с нашим кодом):
Здесь есть ползунок (<input type="range">
), при изменении значения которого кулак Панды стремительно увеличивается.
Все, что вам нужно сделать, – прорезать отверстие в правильном месте.
Решение доступно здесь.
Подсказки
- Выясните, как правильно указать значение атрибута
style
. - Вместо
event.target.value
используйтеevent.target.valueAsNumber
.
У нас есть ещё множество полезных материалов по React для начинающих:
Комментарии