В этой части перевода учебного курса по 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: первое занятие по работе с формами
Занятие 41. Работа с формами, часть 1
→ Оригинал
Формы являются достаточно важной частью веб-приложений. Но, как оказалось, у тех, кто занимается освоением React, работа с формами обычно вызывает определённые сложности. Дело в том, что в React с формами работают по-особенному. На этом занятии мы будем пользоваться стандартным проектом, создаваемым create-react-app, исходный вид файла App.js
которого представлен ниже.
import React, {Component} from "react"
class App extends Component {
constructor() {
super()
this.state = {}
}
render() {
return (
<div>
Code goes here
</div>
)
}
}
export default App
Обратите внимание на то, что для того, чтобы освоить материал этого занятия, вы должны быть знакомы с понятием состояния приложения. Если вы проработали все предыдущие занятия курса и самостоятельно выполняли практикумы, это значит, что вы обладаете знаниями, которые вам здесь понадобятся. Вот документация React, посвящённая формам. Рекомендуется, прежде чем продолжать, взглянуть на неё.
Итак, в React с формами работают немного не так, как при использовании обычного JavaScript. А именно, при обычном подходе формы описывают средствами HTML на веб страницах, после чего, пользуясь API DOM, взаимодействуют с ними из JavaScript. В частности, по нажатию на кнопку отправки формы, собирают данные из полей, заполненных пользователем, и готовят их к отправке на сервер, проверяя их, и, при необходимости, сообщая пользователю о том, что он заполнил какие-то поля неверно. В React же, вместо того, чтобы ожидать ввода всех материалов в поля формы перед тем, как приступить к их программной обработке, за данными наблюдают постоянно, пользуясь состоянием приложения. Это, например, сводится к тому, что каждый символ, введённый пользователем с клавиатуры, сразу же попадает в состояние. В результате в React-приложении мы можем оперативно работать с самой свежей версией того, что пользователь вводит в поля формы. Для того, чтобы продемонстрировать эту идею в действии, начнём с описания формы, содержащей обычное текстовое поле.
Для этого, в коде, который возвращает метод render()
, опишем форму. При обычном подходе такая форма имела бы кнопку, по нажатию на которую программные механизмы приложения приступают к обработке данных, введённых в эту форму. В нашем же случае данные, введённые пользователем в поле, будут поступать в приложение по мере их ввода. Для этого нам понадобится обрабатывать событие поля onChange
. В обработчике этого события (назовём его handleChange()
) мы будем обновлять состояние, записывая в него то, что введено в поле. Для этого нам понадобится, во-первых, узнать, что содержится в поле, и во-вторых — поместить эти данные в состояние. В состоянии создадим свойство, хранящее содержимое поля. Это поле мы собираемся использовать для хранения имени (first name) пользователя, поэтому назовём соответствующее свойство firstName
и инициализируем его пустой строкой.
После этого, в конструкторе, привяжем обработчик события handleChange()
к this
и в коде обработчика воспользуемся функцией setState()
. Так как предыдущее значение, которое хранилось в свойстве состояния firstName
, нас не интересует, мы можем просто передать этой функции объект с новым значением firstName
. Что должно быть записано в это свойство?
Если вспомнить то, как работают обработчики событий в JavaScript, то окажется, что при их вызове им передаются некие предопределённые параметры. В нашем случае обработчику передаётся объект события (event
). Он и содержит интересующие нас данные. Текстовое поле, событие onChange
которого мы обрабатываем, представлен в этом объекте в виде event.target
. А к содержимому этого поля можно обратиться, воспользовавшись конструкцией event.target.value
.
Теперь, в методе render()
, выведем то, что будет храниться в состоянии и посмотрим на то, что у нас получилось.
Вот код, реализующий вышеописанные идеи.
import React, {Component} from "react"
class App extends Component {
constructor() {
super()
this.state = {
firstName: ""
}
this.handleChange = this.handleChange.bind(this)
}
handleChange(event) {
this.setState({
firstName: event.target.value
})
}
render() {
return (
<form>
<input type="text" placeholder="First Name" onChange={this.handleChange} />
<h1>{this.state.firstName}</h1>
</form>
)
}
}
export default App
Вот как всё это выглядит в браузере.
Страница приложения в браузере
Каждый символ, введённый в поле, тут же появляется в элементе <h1>
, присутствующем на странице.
Подумаем теперь о том, как добавить на форму ещё одно поле, для фамилии (last name) пользователя. Очевидно, что для того, чтобы наладить правильную обработку данных, вводимых в это поле, нам понадобится добавить в состояние ещё одно свойство и поработать над механизмами, обновляющими состояние при вводе данных в поле.
Один из подходов к решению этой задачи заключается в создании отдельного обработчика событий для нового поля. Для маленькой формы с несколькими полями ввода это — вполне нормальный подход, но если речь идёт о форме с десятками полей, создавать для каждого из них отдельный обработчик события onChange
— не лучшая идея.
Для того чтобы в одном и том же обработчике событий различать поля, при изменении которых он вызывается, мы назначим полям свойства name
, которые сделаем точно такими же, какими сделаны имена свойств, используемых для хранения данных полей в состоянии (firstName
и lastName
). После этого мы сможем, работая с объектом события, который передаётся обработчику, узнать имя поля, изменения в котором привели к его вызову, и воспользоваться этим именем. Мы будем пользоваться им, задавая имя свойства состояния, в которое хотим внести обновлённые данные. Вот код, который реализует эту возможность:
import React, {Component} from "react"
class App extends Component {
constructor() {
super()
this.state = {
firstName: "",
lastName: ""
}
this.handleChange = this.handleChange.bind(this)
}
handleChange(event) {
this.setState({
[event.target.name]: event.target.value
})
}
render() {
return (
<form>
<input type="text" name="firstName" placeholder="First Name" onChange={this.handleChange} />
<br />
<input type="text" name="lastName" placeholder="Last Name" onChange={this.handleChange} />
<h1>{this.state.firstName} {this.state.lastName}</h1>
</form>
)
}
}
export default App
Обратите внимание на то, что, задавая имя свойства объекта, передаваемого setState()
, мы заключаем конструкцию event.target.name
в прямоугольные скобки.
Страница приложения в браузере
На страницу теперь выводится то, что вводится в первое поле, и то, что вводится во второе поле.
Принципы работы с текстовыми полями, которые мы только что рассмотрели, справедливы и для других полей, основанных на них. Например, это могут быть поля для ввода адресов электронной почты, телефонов, чисел. Данные, вводимые в такие поля, можно обрабатывать, используя вышерассмотренные механизмы, для работы которых важно, чтобы имена полей соответствовали бы именам свойств в состоянии компонента, хранящих данные этих полей.
О работе с другими элементами форм мы поговорим на следующем занятии. Здесь же мы затронем ещё одну тему, которая имеет отношение к так называемым «управляемым компонентам» (controlled component), о которых вы, если посмотрели документацию React по формам, уже кое-что читали.
Если нам нужно чтобы то, что выводится в поле, в точности соответствовало бы тому, что хранится в состоянии приложения, мы можем воспользоваться описанным выше подходом, при применении которого состояние обновляется по мере ввода данных в поле. Состояние является реактивным. А при использовании элементов формы, являющихся управляемыми компонентами, тем, что выводится в этих элементах, управляет состояние. Именно оно при таком подходе является единственным источником истинных данных компонента. Для того чтобы этого добиться, достаточно добавить в код описания элемента формы атрибут value
, указывающий на соответствующее полю свойство состояния. Вот как будет теперь выглядеть код приложения.
import React, {Component} from "react"
class App extends Component {
constructor() {
super()
this.state = {
firstName: "",
lastName: ""
}
this.handleChange = this.handleChange.bind(this)
}
handleChange(event) {
this.setState({
[event.target.name]: event.target.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}
/>
<h1>{this.state.firstName} {this.state.lastName}</h1>
</form>
)
}
}
export default App
После этих изменений приложение работает точно так же, как и раньше. Главное отличие от его предыдущей версии заключается в том, что в поле выводится то, что хранится в состоянии.
Хочу дать один совет, который избавит вас в будущем от ошибок, которые очень тяжело отлаживать. Вот как выглядит код обработчика событий onChange
сейчас:
handleChange(event) {
this.setState({
[event.target.name]: event.target.value
})
}
Рекомендуется, вместо прямого обращения к свойствам объекта event
при конструировании объекта, передаваемого setState()
, заранее извлечь из него то, что нужно:
handleChange(event) {
const {name, value} = event.target
this.setState({
[name]: value
})
}
Здесь мы не будем вдаваться в подробности, касающиеся ошибок, которых можно избежать, конструируя обработчики событий именно так. Если вам это интересно — почитайте о SyntheticEvent в документации React.
Итоги
На этом занятии состоялось ваше первое знакомство с механизмами работы с формами в React. В следующий раз мы продолжим эту тему.
Уважаемые читатели! Пользуетесь ли вы какими-нибудь дополнительными библиотеками при работе с формами в React?
Автор: ru_vds