React.js. Управляемые компоненты
29.07.2021
Теги: Frontend • JavaScript • React.js • Web-разработка • Теория • Форма
В React существует два различных подхода для управления формами:
- Элемент формы
input
, управляемый React — это управляемый компонент - Неуправляемый компонент работает как обычный элемент формы вне React
1. Неуправляемый компонент
Неуправляемый компонент работает как обычный элемент формы, получить введенные пользователем значения можно с помощью ref
.
class Form extends React.Component { constructor(props) { super(props); this.inputRefer = React.createRef(); } handleSubmit = (event) => { alert('Отправленное имя: ' + this.inputRefer.current.value); event.preventDefault(); } render() { return ( <form onSubmit={this.handleSubmit}> <input type="text" name="name" ref={this.inputRefer} /> <input type="submit" value="Отправить" /> </form> ); } }
2. Управляемый компонент
Управляемый компонент принимает свое текущее значение в виде пропсов, а также коллбэк-функцию для изменения этого значения.
<input value={someValue} onChange={handleChange} />
Какой-то компонент хранит состояние полей ввода в своем state
и передает их элементам формы через пропсы.
class Form extends React.Component { constructor() { super(); this.state = { name: '', }; } handleChange = (event) => { this.setState({name: event.target.value}); }; render() { return ( <form> <input type="text" name="name" value={this.state.name} onChange={this.handleChange} /> <input type="submit" value="Отправить" /> </form> ); } }
Атрибут value
всегда показывает значение this.state.name
. Состояние компонента Form
стало единственным «источником истины». Каждое нажатие клавиши вызывает handleChange
и обновляет state
компонента — а значит, будет обновляться и значение value
.
Если полей формы несколько, нет необходимости создавать callback-функцию для каждого. Через event.target
мы можем получить не только value
, но и name
.
class Form extends React.Component { constructor() { super(); this.state = { name: '', surname: '', message: '', }; } handleChange = (event) => { this.setState({[event.target.name]: event.target.value}); }; render() { return ( <form> <input type="text" name="name" value={this.state.name} onChange={this.handleChange} placeholder="Имя" /> <input type="text" name="surname" value={this.state.surname} onChange={this.handleChange} placeholder="Фамилия" /> <textarea name="message" value={this.state.message} onChange={this.handleChange} onBlur={this.validateField} placeholder="Сообщение" /> <input type="submit" value="Отправить" /> </form> ); } }
Здесь мы добавили текстовое поле textarea
— работа с ним ничем не отличается от работы с полем input
.
2.1. Валидация полей формы
Добавим для нашей формы валидацию полей — при потере фокуса будем вызывать функцию validateField
:
class Form extends React.Component { state = { name: '', surname: '', message: '', } handleChange = (event) => { this.setState({[event.target.name]: event.target.value}); } validateField = (event) => { let name = event.target.name; let value = this.state[name]; switch(name) { case 'name': console.log('Проверка имени пользователя'); break; case 'surname': console.log('Проверка фамилии пользователя'); break; case 'message': console.log('Проверка сообщения пользователя'); break; default: console.log('Что-то пошло не так'); } } render() { return ( <form> <input type="text" name="name" value={this.state.name} onChange={this.handleChange} onBlur={this.validateField} placeholder="Имя" /> <input type="text" name="surname" value={this.state.surname} onChange={this.handleChange} onBlur={this.validateField} placeholder="Фамилия" /> <textarea name="message" value={this.state.message} onChange={this.handleChange} onBlur={this.validateField} placeholder="Сообщение" /> <input type="submit" value="Отправить" /> </form> ); } }
2.2. Выпадающий список select
Посмотрим, как работать с выпадающим списком <select>
, чтобы выбранный <option>
сохранялся в state
компонента Form
.
class Form extends React.Component { state = { name: "", surname: "", lang: "", } handleChange = (event) => { this.setState({[event.target.name]: event.target.value}); } validateField = (event) => { let elem = event.target; let name = elem.name; let value = this.state[name]; switch(name) { case 'name': // имя пользователя if (value === "") { elem.style.border = "1px solid red" } else { elem.style.border = "" } break; case 'surname': // фамилия пользователя if (value === "") { elem.style.border = "1px solid red" } else { elem.style.border = "" } break; case 'lang': // выбор языка из списка if (value === "") { elem.style.border = "1px solid red" } else { elem.style.border = "" } break; default: console.log('Что-то пошло не так'); } } render() { return ( <form> <input type="text" name="name" value={this.state.name} onChange={this.handleChange} onBlur={this.validateField} placeholder="Имя" /> <input type="text" name="surname" value={this.state.surname} onChange={this.handleChange} onBlur={this.validateField} placeholder="Фамилия" /> <select name="lang" value={this.state.lang} onChange={this.handleChange} onBlur={this.validateField} > <option value="">Выберите</option> <option value="123">Английский</option> <option value="456">Немецкий</option> <option value="789">Французский</option> </select> <input type="submit" value="Отправить" /> </form> ); } }
Здесь мы работаем с выбранным значением <select>
как со строкой ("123"
, "456"
, "789"
), но можем работать и как с числом.
class Form extends React.Component { state = { name: "", surname: "", lang: 0 } handleChange = (event) => { if (event.target.tagName === "SELECT") { this.setState({[event.target.name]: parseInt(event.target.value)}); } else { this.setState({[event.target.name]: event.target.value}); } } validateField = (event) => { let elem = event.target; let name = elem.name; let value = this.state[name]; switch(name) { case 'name': // имя пользователя if (value === "") { elem.style.border = "1px solid red"; } else { elem.style.border = ""; } break; case 'surname': // фамилия пользователя if (value === "") { elem.style.border = "1px solid red"; } else { elem.style.border = ""; } break; case 'lang': // выбор языка из списка if (value === 0) { elem.style.border = "1px solid red"; } else { elem.style.border = ""; } break; default: alert('Что-то пошло не так'); } } render() { return ( <form> <input type="text" name="name" value={this.state.name} onChange={this.handleChange} onBlur={this.validateField} placeholder="Имя" /> <input type="text" name="surname" value={this.state.surname} onChange={this.handleChange} onBlur={this.validateField} placeholder="Фамилия" /> <select name="lang" value={this.state.lang} onChange={this.handleChange} onBlur={this.validateField} > <option value="0">Выберите</option> <option value="123">Английский</option> <option value="456">Немецкий</option> <option value="789">Французский</option> </select> <input type="submit" value="Отправить" /> </form> ); } }
2.3. Один флажок checkbox
Посмотрим, как работать с одним флажком checkbox
, чтобы сохранять его состояние (отмечен или нет) в state
компонента Form
.
class Form extends React.Component { state = { name: "", surname: "", lang: 0, agree: false, } handleChange = (event) => { // текстовое поле input (имя, фамилия) if (event.target.tagName === "INPUT" && event.target.type === "text") { this.setState({[event.target.name]: event.target.value}); } // выпадающий список select (выбор языка) if (event.target.tagName === "SELECT") { this.setState({[event.target.name]: parseInt(event.target.value)}); } // флажок checkbox (согласие с условиями) if (event.target.tagName === "INPUT" && event.target.type === "checkbox") { this.setState({[event.target.name]: event.target.checked}); } } validateField = (event) => { let elem = event.target; let name = elem.name; let value = this.state[name]; switch(name) { case 'name': // имя пользователя if (value === "") { elem.style.border = "1px solid red"; } else { elem.style.border = ""; } break; case 'surname': // фамилия пользователя if (value === "") { elem.style.border = "1px solid red"; } else { elem.style.border = ""; } break; case 'lang': // выбор языка из списка if (value === 0) { elem.style.border = "1px solid red"; } else { elem.style.border = ""; } break; case 'agree': // согласие с условиями if (!value) { elem.parentElement.style.color = "red"; } else { elem.parentElement.style.color = ""; } break; default: alert('Что-то пошло не так'); } } render() { return ( <form> <input type="text" name="name" value={this.state.name} onChange={this.handleChange} onBlur={this.validateField} placeholder="Имя" /> <input type="text" name="surname" value={this.state.surname} onChange={this.handleChange} onBlur={this.validateField} placeholder="Фамилия" /> <select name="lang" value={this.state.lang} onChange={this.handleChange} onBlur={this.validateField} > <option value="0">Выберите</option> <option value="123">Английский</option> <option value="456">Немецкий</option> <option value="789">Французский</option> </select> <label> <input type="checkbox" name="agree" checked={this.state.agree} onChange={this.handleChange} onBlur={this.validateField} /> Согласен с условиями </label> <input type="submit" value="Отправить" /> </form> ); } }
2.4. Радио-кнопки radio
Наша форма стала слишком большой, так что для следующего примера оставим в ней только радио-кнопки:
class Form extends React.Component { state = { langs: [ {id: 123, value: 'Английский', checked: false}, {id: 456, value: 'Немецкий', checked: false}, {id: 789, value: 'Французский', checked: false}, ] } handleChange = (event) => { const id = parseInt(event.target.value); this.state.langs.forEach((lang, index, langs) => { if (lang.id === id) { langs[index].checked = true; } else { langs[index].checked = false; } }); this.setState({langs: this.state.langs}); } handleSubmit = (event) => { event.preventDefault(); console.log(this.state.langs); } render() { return ( <form onSubmit={this.handleSubmit}> {this.state.langs.map(lang => <label key={lang.id}> <input type="radio" name="langs" value={lang.id} checked={lang.checked} onChange={this.handleChange} /> {lang.value} </label> )} <input type="submit" value="Отправить" /> </form> ); } }
Когда отмечена вторая радио-кнопка, при попытке отправить форму, console.log()
покажет состояние каждой кнопки:
[ {"id": 123, "value": "Английский", "checked": false}, {"id": 456, "value": "Немецкий", "checked": true}, {"id": 789, "value": "Французский", "checked": false} ]
2.5. Несколько флажков checkbox
Часто флажки объединяются в группу с одинаковым значением атрибута name
— это позволяет выбрать несколько значений. Мы должны отследить состояние каждого флажка и сохранить его в state
компонента Form
. Тогда при ajax-отправке данных формы сможем отправить на сервер только те флажки, которые были отмечены пользователем.
class Form extends React.Component { state = { langs: [ {id: 123, value: 'Английский', checked: false}, {id: 456, value: 'Немецкий', checked: false}, {id: 789, value: 'Французский', checked: false}, ] } handleChange = (event) => { const id = parseInt(event.target.value); this.state.langs.forEach((lang, index, langs) => { if (lang.id === id) langs[index].checked = event.target.checked; }); this.setState({langs: this.state.langs}); } handleSubmit = (event) => { event.preventDefault(); console.log(this.state.langs); } render() { return ( <form onSubmit={this.handleSubmit}> {this.state.langs.map(lang => <label key={lang.id}> <input type="checkbox" name="langs" value={lang.id} checked={lang.checked} onChange={this.handleChange} /> {lang.value} </label> )} <input type="submit" value="Отправить" /> </form> ); } }
Когда отмечены первый и третий флажки, при попытке отправить форму, console.log()
покажет состояние каждого флажка:
[ {"id": 123, "value": "Английский", "checked": true}, {"id": 456, "value": "Немецкий", "checked": false}, {"id": 789, "value": "Французский", "checked": true} ]
langs=123&langs=789
. Сервер должен правильно обработать такое значение, чтобы последнее значение не затерло все предыдущие.
2.6. Список select multiple
Список мы уже рассматривали, но пропустили вариант, который допускает выбор нескольких элементов — это возможно, когда у списка есть атрибут multiple
.
class Form extends React.Component { state = { langs: [ {id: 123, value: 'Английский', selected: false}, {id: 456, value: 'Немецкий', selected: false}, {id: 789, value: 'Французский', selected: false}, ] } handleChange = (event) => { const options = event.target.options; this.state.langs.forEach((lang, index, langs) => { langs[index].selected = options[index].selected; }); this.setState({langs: this.state.langs}); } handleSubmit = (event) => { event.preventDefault(); console.log(this.state.langs); } render() { const selected = []; this.state.langs.forEach(lang => { if (lang.selected) selected.push(lang.id); }); return ( <form onSubmit={this.handleSubmit}> <select name="langs" value={selected} onChange={this.handleChange} multiple={true} size="3" > {this.state.langs.map(lang => <option key={lang.id} value={lang.id}>{lang.value}</option> )} </select> <input type="submit" value="Отправить" /> </form> ); } }
Когда отмечены первый и третий элементы списка, при попытке отправить форму, console.log()
покажет состояние каждого элемента:
[ {"id": 123, "value": "Английский", "selected": true}, {"id": 456, "value": "Немецкий", "selected": false}, {"id": 789, "value": "Французский", "selected": true} ]
Поиск: JavaScript • React.js • Web-разработка • Frontend • Теория • Форма • Управляемый компонент