Сегодня мы продолжим разговор об использовании форм в React. В прошлый раз мы рассматривали особенности взаимодействия компонентов и текстовых полей. Здесь же мы обсудим работу с другими элементами форм.
→ Часть 1: обзор курса, причины популярности React, ReactDOM и JSX
→ Часть 2: функциональные компоненты
→ Часть 3: файлы компонентов, структура проектов
→ Часть 4: родительские и дочерние компоненты
→ Часть 5: начало работы над TODO-приложением, основы стилизации
→ Часть 6: о некоторых особенностях курса, JSX и JavaScript
→ Часть 7: встроенные стили
→ Часть 8: продолжение работы над TODO-приложением, знакомство со свойствами компонентов
→ Часть 9: свойства компонентов
→ Часть 10: практикум по работе со свойствами компонентов и стилизации
→ Часть 11: динамическое формирование разметки и метод массивов map
→ Часть 12: практикум, третий этап работы над TODO-приложением
→ Часть 13: компоненты, основанные на классах
→ Часть 14: практикум по компонентам, основанным на классах, состояние компонентов
→ Часть 15: практикумы по работе с состоянием компонентов
→ Часть 16: четвёртый этап работы над TODO-приложением, обработка событий
→ Часть 17: пятый этап работы над TODO-приложением, модификация состояния компонентов
→ Часть 18: шестой этап работы над TODO-приложением
→ Часть 19: методы жизненного цикла компонентов
→ Часть 20: первое занятие по условному рендерингу
→ Часть 21: второе занятие и практикум по условному рендерингу
→ Часть 22: седьмой этап работы над TODO-приложением, загрузка данных из внешних источников
→ Часть 23: первое занятие по работе с формами
→ Часть 24: второе занятие по работе с формами
Занятие 42. Работа с формами, часть 2
→ Оригинал
На этом занятии мы поговорим о полях для ввода многострочного текста, о флажках, о переключателях (их ещё называют «радиокнопками») и о полях со списками. К настоящему моменту мы рассмотрели лишь работу с обычными текстовыми полями ввода.
Вот код компонента App
, с которого мы начнём сегодняшние эксперименты:
import React, {Component} from "react"
class App extends Component {
constructor() {
super()
this.state = {
firstName: "",
lastName: ""
}
this.handleChange = this.handleChange.bind(this)
}
handleChange(event) {
const {name, value} = event.target
this.setState({
[name]: value
})
}
render() {
return (
<form>
<input
type="text"
value={this.state.firstName}
name="firstName"
placeholder="First Name"
onChange={this.handleChange}
/>
<br />
<input
type="text"
value={this.state.lastName}
name="lastName"
placeholder="Last Name"
onChange={this.handleChange}
/>
{
/**
* Другие полезные элементы форм:
*
* <textarea />
* <input type="checkbox" />
* <input type="radio" />
* <select> и <option>
*/
}
<h1>{this.state.firstName} {this.state.lastName}</h1>
</form>
)
}
}
export default App
Вот как выглядит страница приложения в браузере на данном этапе работы.
Страница приложения в браузере
Формы обычно содержат в себе не только поля, в которые вводят короткие строки. При оснащении форм другими элементами работа с ними в React немного усложняется, хотя ничего особенного в этом нет.
В вышеприведённом коде есть закомментированный фрагмент, в котором перечислены элементы, о которых мы будем говорить. Начнём с поля для ввода многострочного текста — элемента textarea
. Вероятно, понять то, как с ним работать, легче всего. Если вы раньше, строя обычные HTML-формы, уже пользовались этим элементом, вы знаете, что это — не самозакрывающийся тег, как было в случае с элементом input
. У него есть открывающая и закрывающая части.
Добавим на форму этот элемент, вставив, сразу после комментария, следующий код:
<br />
<textarea></textarea>
Если теперь взглянуть на страницу приложения, то можно видеть, как на ней появилось поле для ввода многострочного текста.
Поле для ввода текста на странице
Как видно, это поле немного выше обычных полей, пользователь может менять его размеры, пользуясь маркером в его правой нижней части. Благодаря атрибутам rows
и cols
можно, при описании этого элемента, указывать его размеры. В обычном HTML, если нужно, чтобы после вывода этого поля в нём уже был какой-то текст, делается это путём ввода нужного текста между открывающим и закрывающим тегами элемента. В React работа с такими элементами сделана максимально похожей на работу с элементами input
, о которых мы говорили в прошлый раз. А именно, в React тег textarea
является самозакрывающимся. То есть код для вывода поля на страницу можно изменить следующим образом:
<textarea />
В этом теге можно использовать атрибут value
, причём, работа с ним осуществляется точно так же, как и с таким же атрибутом обычных текстовых полей. Благодаря этому достигается единообразие в работе с разными элементами, и, кроме того, облегчается обновление содержимого полей путём обновления свойств состояния, привязанных к таким полям. Приведём состояние кода поля к такому виду:
<textarea value={"Some default value"}/>
Это приведёт к тому, что указанный текст появится в поле при выводе его на страницу.
Текст, появившийся в поле
К работе с полем для ввода многострочного текста мы ещё вернёмся, а пока поговорим о флажках. Флажок — это элемент управления input
, в качестве типа которого указан checkbox
. Вот как выглядит его описание:
<input type="checkbox" />
Вот как выглядит флажок, описанный этой разметкой, на странице.
Флажок
Основной особенностью этого элемента управления является тот факт, что при работе с ним не используется атрибут value
. Его применяют для того, чтобы предоставить пользователю выбор их неких двух вариантов, один из которых соответствует установленному флажку, а другой — снятому. Для отслеживания того, установлен флажок или снят, используется атрибут checked
, который описывается логическим значением. В результате флажкам обычно соответствуют логические свойства, хранящиеся в состоянии.
Приведём состояние компонента к такому виду:
this.state = {
firstName: "",
lastName: "",
isFriendly: true
}
Код описания флажка изменим следующим образом:
<input
type="checkbox"
checked={this.state.isFriendly}
/>
После этого на страницу будет выведен установленный флажок.
Установленный флажок
Правда, сейчас он не будет реагировать на щелчки по нему. Дело в том, что флажок привязан к соответствующей переменной, хранящейся в состоянии, в результате при попытке, в нашем случае, снять его, React, проверяя состояние, и обнаруживая, что свойство isFriendly
установлено в true
, не даёт этого сделать. При этом в консоль будет выводиться предупреждение о том, что мы не предусмотрели механизм изменения поля (обработчик события onChange
) и оно выведено в состоянии «только для чтения».
Предупреждение в консоли
Мы вполне можем написать особый метод для работы с флажком, но в коде нашего компонента уже есть метод handleChange()
. Сейчас он используется для работы с текстовыми полями. Подумаем о том, как воспользоваться им и для работы с флажком. Для этого сначала назначим вышеуказанный метод в качестве обработчика события onChange
флажка и назначим флажку имя, соответствующее имени свойства состояния, относящегося к флажку. Кроме того, подпишем флажок, воспользовавшись тегом label
:
<label>
<input
type="checkbox"
name="isFriendly"
checked={this.state.isFriendly}
onChange={this.handleChange}
/> Is friendly?
</label>
В методе handleChange()
, код которого показан ниже, мы, при работе с текстовыми полями, выясняли имя элемента (name
) и его содержимое (value
), после чего обновляли состояние, записывая в него то, что было у поля с определённым именем в его атрибуте value
:
handleChange(event) {
const {name, value} = event.target
this.setState({
[name]: value
})
}
Теперь нам нужно разобраться с тем, как быть с флажком, атрибута value
у которого нет. У него есть лишь атрибут checked
, который может принимать только значения true
или false
. В результате нам, для того чтобы использовать метод handleChange()
для работы с флажком, нужно проверить, является ли элемент, для которого вызван этот обработчик, флажком. Для того чтобы выполнить эту проверку — вспомним, что в качестве типа (type
) элемента input
, представляющего флажок, задано значение checkbox
. Для того чтобы проверить это значение, можно обратиться к свойству type
элемента event.target
. Извлечём это свойство из event.target
, а также — свойство checked
, воспользовавшись следующей конструкцией:
const {name, value, type, checked} = event.target
Теперь мы можем проверить значение константы type
и выяснить, является ли элемент, для которого вызван обработчик события, флажком. Если это так — мы запишем в состояние то, что оказалось в константе checked
. Не забудем при этом сохранить код, ответственный за работу с текстовыми полями. В результате код handleChange()
приобретёт следующий вид:
handleChange(event) {
const {name, value, type, checked} = event.target
type === "checkbox" ? this.setState({ [name]: checked }) : this.setState({ [name]: value })
}
После этого проверим работу флажка.
Проверка работы флажка
Как видно, теперь его можно снимать и устанавливать. При этом работа текстовых полей не нарушена. Из консоли исчезло уведомление, касающееся флажка, но там выводится уведомление, касающееся поля для ввода многострочного текста. Изменим код, описывающий это поле, следующим образом:
<textarea
value={"Some default value"}
onChange={this.handleChange}
/>
Это приведёт к исчезновению уведомления, хотя другие механизмы, позволяющие работать с этим полем средствами компонента, мы не реализовали (не указали имя для поля, не добавили в состояние соответствующее свойство). Вы можете реализовать эти возможности самостоятельно. Теперь поговорим о переключателях.
Их можно представить в виде комбинации элементов input
типов text
и checkbox
. Здесь имеется в виду то, что у переключателей есть и атрибут value
, и атрибут checked
. Добавим в нашу форму пару переключателей, создав их код на основе кода описания флажка. Вот как это выглядит:
<label>
<input
type="radio"
name="gender"
value="male"
checked={this.state.isFriendly}
onChange={this.handleChange}
/> Male
</label>
<br />
<label>
<input
type="radio"
name="gender"
value="female"
checked={this.state.isFriendly}
onChange={this.handleChange}
/> Female
</label>
Мы создали этот код на основе кода описания флажка и кое-что ещё не отредактировали. Поэтому переключатели странно себя ведут. В частности, если флажок снят, то и тот и другой переключатели находятся в «выключенном» состоянии, а если установить флажок — один из них оказывается «включенным». Подобные ошибки можно предупредить, внимательно относясь к коду элементов в том случае, если он создаётся на основе кода уже существующих элементов. Сейчас мы это исправим.
Обратите внимание на то, что у этих двух элементов одно и то же имя — gender
. Переключатели с одним и тем же именем формируют группу. Выбранным может быть лишь один переключатель, входящий в такую группу.
При настройке переключателей нельзя просто указать на то, что их значение checked
устанавливается, скажем, в true
, если некое свойство состояния равняется true
. Переключатели должны поддерживать синхронизированное, в пределах группы, изменение собственного состояния. Вместо этого значение checked
переключателей устанавливается по условию. Это условие в нашем случае будет представлено сравнением свойства состояния this.state.gender
со строкой male
или female
. В коде описания переключателей это выглядит так:
<label>
<input
type="radio"
name="gender"
value="male"
checked={this.state.gender === "male"}
onChange={this.handleChange}
/> Male
</label>
<br />
<label>
<input
type="radio"
name="gender"
value="female"
checked={this.state.gender === "female"}
onChange={this.handleChange}
/> Female
</label>
Теперь добавим в состояние новое свойство, gender
, инициализировав его пустой строкой:
this.state = {
firstName: "",
lastName: "",
isFriendly: false,
gender: ""
}
После этого переключатели будут работать независимо от флажка. Добавим в код, выводимый компонентом, заголовок второго уровня, выводящий сведения о том, какой именно переключатель выбран:
<h2><font color="#000">You are a {this.state.gender}</font></h2>
Тут, вероятно, стоит внедрить некий механизм условного рендеринга. Это позволит, при открытии страницы, когда ни один из переключателей не выбран, сделать так, чтобы на ней не выводился бы текст You are a
, но мы этого делать не будем, хотя вы вполне можете реализовать это самостоятельно. Взглянем теперь на то, что у нас получилось.
Переключатели на странице приложения
Всё то, о чём мы тут говорили, может показаться достаточно сложным. В частности, это касается запоминания особенностей разных элементов управления. Для того чтобы упростить работу с формами, можно применять специализированные библиотеки. Например — библиотеку formik. Эта библиотека значительно упрощает процесс разработки форм в React-приложениях.
Теперь поговорим о полях со списками.
В обычном HTML при описании полей со списком используются такие конструкции:
<select>
<option></option>
<option></option>
<option></option>
<select/>
В React применяется похожий подход, хотя, как и в случае с другими элементами, используется атрибут value
. Это позволяет легко выяснить то, какой именно элемент списка выбран, и, кроме того, это облегчает работу с состоянием компонента.
Предположим, мы хотим создать поле со списком, позволяющее пользователю выбирать его любимый цвет. Для этого в атрибут value
элемента select
можно поместить следующую конструкцию: value={this.state.favColor}
. Сюда будут попадать те значения, которые будет выбирать пользователь. Теперь добавим favColor
в состояние:
this.state = {
firstName: "",
lastName: "",
isFriendly: false,
gender: "",
favColor: "blue"
}
Далее, оснастим поле со списком обработчиком события onChange
и дадим ему имя. Также назначим значения value
элементам options
поля со списком и введём текст, который будет выводиться в поле.
Вот как выглядит настроенный элемент select
с подписью:
<label>Favorite Color:</label>
<select
value={this.state.favColor}
onChange={this.handleChange}
name="favColor"
>
<option value="blue">Blue</option>
<option value="green">Green</option>
<option value="red">Red</option>
<option value="orange">Orange</option>
<option value="yellow">Yellow</option>
</select>
Теперь добавим в форму ещё одну надпись, выводящую любимый цвет пользователя:
<h2><font color="#000">Your favorite color is {this.state.favColor}</font></h2>
Пришло время испытать поле со списком.
Поле со списком
Как видно, хотя наша форма и не блещет изысками дизайна, элементы управления, размещённые на ней, работают так, как ожидается.
Благодаря тому, как в React организованы API элементов управления, несложно сделать так, чтобы для обработки их событий применялся бы один и тот же обработчик. Именно такая схема работы используется и в нашем случае. Единственная особенность нашего обработчика handleChange()
заключается в том, что нам приходится по-особенному обрабатывать события флажка.
Теперь поговорим об отправке формы или об обработке введённых в неё значений после завершения её заполнения. Можно выделить два подхода к выполнению подобных действий. При применении любого из них форму стоит оснастить кнопкой:
<button>Submit</button>
В HTML5, если в форме будет найден элемент button
, он будет действовать как старый элемент input
с типом submit
. Если по этой кнопке щёлкнуть — будет вызвано событие самой формы onSubmit
. Если нужно что-то сделать после завершения заполнения формы, можно добавить обработчик события onClick
к кнопке, но, например, лично я предпочитаю обрабатывать подобные события на уровне формы, назначая ей обработчик события onSubmit
:
<form onSubmit={this.handleSubmit}>
Метод, используемый в качестве обработчика этого события, ещё не написан. Это — обычный обработчик события, который, например, обращаясь к некоему API, передаёт ему данные формы.
Итоги
На этом занятии мы завершаем разговор о работе с формами в React. В следующий раз вас ждёт практическая работа по этой теме.
Уважаемые читатели! Если вы пробовали использовать библиотеку formik для создания форм в React — просим об этом рассказать.
Автор: ru_vds