От автора: формы являются неотъемлемой частью любого современного приложения. Они служат основным средством взаимодействия пользователей с вашим приложением. Разработчики полагаются на формы для всего: безопасный вход в систему пользователя, поиск и фильтрация списка продуктов, бронирование продукта и построение корзины и т. Д. Более сложные приложения, созданные для предприятий, обычно более интенсивно заполняются, при этом поля ввода охватывают несколько вкладок. Кроме того, нужно учесть и развернуть логику проверки.
В этом уроке мы рассмотрим, как обрабатывает React формы. Мы рассмотрим не только основы, но также сформируем валидацию и передовые методы — даже опытные разработчики неправильно понимают определенные детали.
Создание формы — контролируемый или неконтролируемый компонент
React предлагает устойчивый, реактивный подход к созданию форм. В отличие от других элементов DOM, HTML элементы формы работают по-разному в React. Данные формы, например, обычно обрабатываются компонентом, а не DOM, и обычно реализуются с использованием контролируемых компонентов. На изображении ниже прекрасно описывается, как контролируемые компоненты работают в React.
Структура формы аналогична структуре обычных HTML-форм. Однако каждый элемент ввода получает свой собственный компонент, который мы называем немым компонентом. Контейнерный компонент отвечает за поддержание состояния. Разница заключается в том, что мы используем функцию обратного вызова для обработки событий формы, а затем с использованием состояния контейнера для хранения данных формы. Это дает вашему компоненту лучший контроль над элементами управления формой и данными формы.
Функция обратного вызова активируется на событиях, включая изменение значений управления формой или при отправке формы. Затем функция подталкивает значения формы в локальное состояние компонента, а затем данные управляются компонентом. Поскольку мы используем атрибут value в элементе формы, отображаемое значение будет значением this.state.value.
Существует еще один метод, известный как неконтролируемые компоненты, для создания входных форм. Это больше похоже на традиционные HTML-формы, поскольку данные входной формы хранятся внутри DOM, а не внутри компонента. Элементы типа input и textarea сохраняют свое собственное состояние, которое они обновляют при изменении входных значений. Вы можете запросить DOM значения поля ввода с помощью ссылки.
Вот пример из официальной документации, демонстрирующий работу неконтролируемых компонентов.
class NameForm extends Component { constructor(props) { super(props); this.handleSubmit = this.handleSubmit.bind(this); } handleSubmit(e) { alert('The value is: ' + this.input.value); e.preventDefault(); } render() { return ( <form onSubmit={this.handleSubmit}> <label> Name: <input type="text" ref={(input) => this.input = input} /> </label> <input type="submit" value="Submit" /> </form> ); } }
Здесь компонент input отвечает за сохранение своего состояния. Атрибут ref создает ссылку на доступный узел DOM, и вы можете потянуть это значение, когда вам это нужно, — когда вы собираетесь отправить форму в примере.
React рекомендует использовать контролируемые компоненты над refs для реализации форм. Refs предлагает backdoor для DOM, который может соблазнить вас использовать его для выполнения jQuery. С другой стороны, контролируемые компоненты более просты — данные формы обрабатываются компонентом React. Однако, если вы хотите интегрировать React с проектом non-React или создать по какой-либо причине быструю и легкую форму, вы можете использовать ref. Остальная часть этого урока будет сосредоточена на контролируемых компонентах.
Демо формы React
Вот демо-версия codandobx для формы, которую мы будем создавать сегодня. @ React Form Demo
Вы также можете взять копию кода из моего репозитория GitHub. Скройте репозиторий, запустите npm install и запустите npm start.
Структурирование формы
Модель композиции React позволяет упорядочить ваш код на более мелкие повторно используемые компоненты. Каждый компонент существует как независимый функциональный блок, и иерархия компонентов может использоваться для представления конкретной функции. Эта структура особенно хорошо работает с формами. Вы можете создавать пользовательские компоненты для input, textarea, select и т. д. и повторно использовать их для FormContainer компонента FormContainer.
Примечание: хотя может возникнуть соблазн использовать библиотеку форм, вероятность того, что вы столкнетесь с препятствиями, когда вам нужно добавить пользовательское поведение и валидацию — это высокая вероятность. Создание компонента многократного использования с нуля поможет вам укрепить понимание форм React.
import React, { Component } from 'react'; import './styles.css'; import FormContainer from './containers/FormContainer'; class App extends Component { render() { return ( <div className="container"> <h3>React Form</h3> <FormContainer /> </div> ); } } export default App;
FormContainer — это компонент контейнера, который отображает все элементы формы и обрабатывает всю бизнес-логику. Мы называем его компонентом-контейнером, потому что он заботится об обновлении состояния формы, обработке формы и обработке вызовов API/диспетчеризации Redux. Немые компоненты или презентационные компоненты связаны с тем, как вещи выглядят и содержат фактическую разметку DOM. Эти компоненты получают данные и обратные вызовы исключительно в качестве реквизита.
Давайте создадим контейнер:
import React, {Component} from 'react'; /* Import Components */ import CheckBox from '../components/CheckBox'; import Input from '../components/Input'; import TextArea from '../components/TextArea'; import Select from '../components/Select'; import Button from '../components/Button' class FormContainer extends Component { constructor(props) { super(props); this.state = { newUser: { name: '', email: '', age: '', gender: '', expertise: '', about: '' }, genderOptions: ['Male', 'Female', 'Others'], skillOptions: ['Programming', 'Development', 'Design', 'Testing'] } this.handleFormSubmit = this.handleFormSubmit.bind(this); this.handleClearForm = this.handleClearForm.bind(this); } /* This life cycle hook gets executed when the component mounts */ handleFormSubmit() { // Form submission logic } handleClearForm() { // Logic for resetting the form } render() { return ( <form className="container" onSubmit={this.handleFormSubmit}> <Input /> {/* Name of the user */} <Input /> {/* Input for Age */} <Select /> {/* Gender Selection */} <CheckBox /> {/* List of Skills (eg. Programmer, developer) */} <TextArea /> {/* About you */} <Button /> { /*Submit */ } <Button /> {/* Clear the form */} </form> ); } } export default FormContainer; }
Во-первых, мы импортировали немые компоненты из каталога компонентов. Компоненты включают в себя Input, Select, CheckBox, TextArea и Button.
Затем мы инициировали состояние для хранения пользовательских данных и данных пользовательского интерфейса. Для обработки логики формы были созданы два метода — handleFormSubmit() и handleClearForm(). Метод render отображает все поля ввода и кнопки, необходимые для нашей формы регистрации.
Составление немых компонентов
Мы изложили структуру формы. Затем нам нужно составить дочерние компоненты. Давайте рассмотрим компоненты один за другим.
<Input />
Компонент <Input /> отображает однострочное поле ввода. Тип ввода может быть либо текстовым, либо цифровым. Давайте посмотрим на реквизиты, что нам нужно создать компонент <Input />.
type — type prop определяет, будет ли отображаемое поле ввода иметь текстовый или числовой тип. Например, если значение type равно числу, тогда будет отображаться <input type=»number» />. В противном случае выводится <input type=»text» />.
title — Значение заголовка будет отображаться как метка этого конкретного поля.
name — это атрибут name для ввода.
value — Значение (текст или число), которое должно отображаться внутри поля ввода. Вы можете использовать эту опцию, чтобы указать значение по умолчанию.
placeholder — необязательная строка, которую вы можете передать, чтобы поле ввода отображало текст заполнителя.
handleChange — управляющая функция, которая срабатывает при изменении значения элемента управления вводом. Затем функция обновляет состояние родительского компонента и передает новое значение через значение prop.
Вот код для компонента <Input />. Обратите внимание, что здесь мы используем функциональные компоненты без состояния.
const Input = (props) => { return ( <div className="form-group"> <label htmlFor={props.name} className="form-label">{props.title}</label> <input className="form-input" id={props.name} name={props.name} type={props.type} value={props.value} onChange={props.handleChange} placeholder={props.placeholder} /> </div> ) } export default Input;
Вы можете расширить список возможных атрибутов и добавить их в качестве props. Вот как выглядит заявление компонента:
<Input type={'text'} title= {'Full Name'} name= {'name'} value={this.state.newUser.name} placeholder = {'Enter your name'} handleChange = {this.handleFullName} /> {/* Name of the user */}
Колбек handleChange заботится об обновлении состояния, а обновленное значение распространяется через props.value. Я назову функцию обратного вызова handleFullName.
/* FormContainer.jsx */ //... handleFullName(e) { let value = e.target.value; this.setState( prevState => ({ newUser : {...prevState.newUser, name: value } })) } //...
setState принимает объект или функцию обновления со следующей подписью.
(prevState, props) => stateChange
Объект prevState сохраняет актуальное значение предыдущего состояния. Мы собираемся объединить обновленные значения с предыдущим состоянием.
Примечание: в JavaScript методы класса не связаны по умолчанию. Вам нужно будет привязать его вручную. Что это значит? Вам нужно будет добавить привязку в конструктор для каждого метода класса, и привязка будет выглядеть так:
this.handleFullName = this.handleFullName.bind(this)
Кроме того, вы можете использовать поля классов для привязки вне конструктора. Эта функция все еще находится в экспериментальной фазе, поэтому для поддержки вам нужно будет установить плагин для babel transform-class-properties.
Следующее поле ввода будет для возраста. Логика handleAge будет аналогична handleFullName метода handleFullName.
/* FormContainer.jsx */ handleAge(e) { let value = e.target.value; this.setState( prevState => ({ newUser : {...prevState.newUser, age: value } }), () => console.log(this.state.newUser)) }
Этот метод обновляет состояние this.state.newUser.age. Хотя этот подход нормальный, вы можете реорганизовать код и создать общий метод обработчика, который работает для всех компонентов <Input />.
/* FormContainer.jsx */ handleInput(e) { let value = e.target.value; let name = e.target.name; this.setState( prevState => { return { newUser : { ...prevState.newUser, [name]: value } } }, () => console.log(this.state.newUser) ) }
handleInput() заменит как handleFullName(), так и handleAge(). Единственное изменение, которое мы сделали, это извлекли значение имени из переменной формы и затем использовали эти данные для установки состояния. Таким образом, значение имени prop должно быть таким же, как ключ свойства в состоянии.
Далее <Select />.
<Select />
Компонент <Select /> отображает список раскрывающихся элементов. Обычно для раскрывающегося списка есть текст заполнителя или значение по умолчанию. Свойства <Select />:
title — Значение заголовка title будет отображаться как метка элемента select.
name — Атрибут name для элемента select.
options — массив доступных опций. Например, мы используем <Select /> для отображения раскрывающегося списка вариантов пола.
value — Значение prop может использоваться для установки значения по умолчанию для поля.
placeholder — короткая строка, которая заполняет первый option.
handleChange — управляющая функция, которая срабатывает при изменении значения элемента управления вводом. Затем функция обновляет состояние родительского компонента и передает новое значение через значение prop.
Давайте посмотрим на фактический код компонента <Select />.
/*Select.jsx*/ const Select = (props) => { return( <div className="form-group"> <label htmlFor={props.name}> {props.title} </label> <select name={props.name} value={props.value} onChange={props.handleChange} > <option value="" disabled>{props.placeholder}</option> {props.options.map(option => { return ( <option key={option} value={option} label={option}>{option} </option> ); })} </select> </div>) } export default Select;
Первый тег параметра заполняется строкой-заполнителем. Остальные параметры отображаются из массива, который мы передали в качестве prop. При использовании метода map для итерации через элементы DOM не забудьте добавить уникальный атрибут key который является уникальным. Это помогает реагировать на отслеживание обновлений DOM. Если вы не укажете key атрибут, вы увидите предупреждение в своем браузере и можете столкнуться с проблемами производительности в будущем.
Теперь давайте посмотрим на функцию обратного вызова. Логика метода аналогична логике метода generic handle, который мы создали ранее. Мы можем фактически подключить этот метод обработчика в качестве опоры, и все должно работать так, как ожидалось.
<Select title={'Gender'} name={'gender'} options = {this.state.genderOptions} value = {this.state.newUser.gender} placeholder = {'Select Gender'} handleChange = {this.handleInput} /> {/* Age Selection */}
<CheckBox/>
Флажки могут казаться немного более сложными, поскольку задействованы массивы. Но как CheckBox и Select схожи в отношении props. Основное различие заключается в том, как обновляется состояние. Давайте сначала посмотрим на props.
title — уже обговорили.
name — уже обговорили.
options — массив доступных опций. Массив обычно состоит из строк, которые в конечном итоге являются меткой и значением каждого флажка.
selectedOptions — массив выбранных значений. Если пользователь заранее выбрал определенные варианты, массив selectedOptions будет заполнен этими значениями. Это является синонимом подсказки значения параметра <Select />.
handleChange — уже обговорили.
Компонент CheckBox.
/* CheckBox.jsx */ const CheckBox = (props) => { return( <div> <label for={props.name} className="form-label">{props.title}</label> <div className="checkbox-group"> {props.options.map(option => { return ( <label key={option}> <input className="form-checkbox" id = {props.name} name={props.name} onChange={props.handleChange} value={option} checked={ props.selectedOptions.indexOf(option) > -1 } type="checkbox" /> {option} </label> ); })} </div> </div> ); }
Строка checked={ props.selectedOptions.indexOf(option) > -1 } может быть запутанной, если раньше вы никогда не использовали метод indexOf JavaScript. indexOf проверяет, существует ли конкретный элемент в массиве и возвращает его индекс. Предполагая, что этот параметр содержит строку, он проверяет, существует ли строка в selectedOptions, и если элемент не существует в массиве, он вернет -1. Это самый простой способ заполнить значения для группы флажков в форме.
Поскольку нам нужно вывести массив в состояние, которое сложнее обычного handleInput(), давайте создадим новый метод обработки чекбоксов.
handleSkillsCheckBox(e) { const newSelection = e.target.value; let newSelectionArray; if(this.state.newUser.skills.indexOf(newSelection) > -1) { newSelectionArray = this.state.newUser.skills.filter(s => s !== newSelection) } else { newSelectionArray = [...this.state.newUser.skills, newSelection]; } this.setState( prevState => ({ newUser: {...prevState.newUser, skills: newSelectionArray } }) ) }
Пользователь может взаимодействовать с флажком двумя способами — выделить элемент или снять отметку с существующего элемента. Это взаимодействие пользователя соответствует двум действиям — добавлению элемента в массив или удалению существующего элемента из массива.
Значение newSelection имеет значение только что выбранного (или отмененного) элемента. Мы сравниваем его с существующим выбором элементов, хранящихся в this.state.newUser.skills . Мы снова будем полагаться на indexOf чтобы проверить, находится ли строка, хранящаяся в newSelection, в массиве.
Если это часть массива, условие выполняется, и новый элемент выбора отфильтровывается и сохраняется в newSelection. В противном случае элемент newSelection объединяется в массив с помощью оператора spread.
Наконец, состояние обновляется с помощью this.setState.
<TextArea />
Я собираюсь оставить это как упражнение для читателя. Это довольно похоже на компонент <Input />, который мы создали ранее. Элемент <textarea /> должен принимать дополнительные реквизиты для строк и столбцов. Код для компонента TextArea доступен в демо.
<Button />
Кнопки являются самыми легкими. Вы можете поддерживать компонент <Button /> достаточно простым и легким. Вот список props, который требуется кнопке:
title — Текст для кнопки.
action — функция обратного вызова
style — объекты стиля могут передаваться как prop.
<Button/> в действии:
/*Button.jsx */ const Button = (props) => { console.log(props.style); return( <button style= {props.style} onClick= {props.action}> {props.title} </button>) } export default Button;
Действия формы — handleClearForm и handleFormSubmit
Мы почти дошли до конца. Последний шаг — составить действия формы. Поскольку компонент FormContainer поддерживает состояние, туда будут действовать методы действия формы. Метод handleClearForm очистит состояние и вернет его исходным значениям.
handleClearForm(e) { e.preventDefault(); this.setState({ newUser: { name: '', age: '', gender: '', skills: [], about: '' }, }) }
Строка e.preventDefault() предотвращает обновление страницы при e.preventDefault() формы, что является поведением формы по умолчанию.
Метод handleFormSubmit() посылает запросы AJAX на сервер. Данные, которые необходимо отправить, доступны в this.state.newUser. Существует множество библиотек, которые вы можете использовать для совершения AJAX-запросов. Я собираюсь использовать выборку здесь.
handleFormSubmit(e) { e.preventDefault(); let userData = this.state.newUser; fetch('http://example.com',{ method: "POST", body: JSON.stringify(userData), headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' }, }).then(response => { response.json().then(data =>{ console.log("Successful" + data); }) }) }
Вот и все!
Заключение
В этой статье мы рассмотрели все, что вам нужно знать о создании форм в React. React имеет компонентную архитектуру, и компоненты предназначены для повторного использования. Мы создали компоненты для таких элементов: <input/>, <select />, <textArea/> и т. д. Вы можете дополнительно настроить компоненты в соответствии с вашими требованиями, передав больше props.
Надеюсь, вы хорошо читали статью. Что вы думаете о создании форм в React? Делитесь своим опытом в комментариях.
Автор: Manjunath
Источник: https://www.codementor.io/
Редакция: Команда webformyself.