Привет, Хабр. Представляю вам перевод статьи «Creating forms in React in 2020» автора Kristofer Selbekk
Поля ввода. Текстовые области. Радиокнопки и чекбоксы. Вот некоторые из основных точек взаимодействия, которые мы, как разработчики, имеем с нашими пользователями. Мы размещаем их на сайте, пользователи их заполняют и если нам повезёт, то заполненные формы приходят к нам без ошибок валидации.
Обработка форм является неотъемлемой частью большого количества веб-приложений, и это одна из вещей, которые React делает лучше всего. У вас есть большая свобода для создания и обработки форм. Но есть ли лучший способ сделать это?
Обратите внимание, что во всех этих примерах мы создадим форму входа с электронной почтой и полем пароля, но эти методы можно использовать с большинством типов форм.
Хотя это не имеет прямого отношения к обсуждаемой теме, я хочу убедиться, что вы не забыли сделать свои формы понятными для всех. Добавьте теги к своим полям ввода, установите правильные aria-атрибуты для случаев, когда ввод недопустим, и структурируйте ваш контент семантически правильно. Это облегчает использование вашей формы как для пользователей, так и для разработчиков.
Для начала давайте посмотрим, как я обычно работаю с состоянием формы. Я сохраняю все поля как отдельные элементы состояния и обновляю их все по отдельности, что выглядит примерно так:
Сначала я создаю две отдельные части состояния — имя пользователя и пароль. Эти две переменные затем передаются в соответствующее поле ввода, определяя значение этого поля. Всякий раз, когда что-то в поле изменяется, мы обязательно обновляем значение состояния, вызывая повторную перерисовку нашего приложения.
Такая обработка форм отлично работает в большинстве случаев и является простой и легкой в использовании и понимании. Тем не менее, довольно утомительно писать такую конструкцию каждый раз.
Давайте сделаем небольшой рефакторинг и создадим собственный хук, который немного улучшит наш код:
Мы создаем пользовательский хук useFormField, который создает для нас обработчик события onChange, а также сохраняет значение в состояние формы. В этом случае мы можем использовать наш хук со всеми полями формы.
Недостатком этого подхода является то, что он плохо масштабируется по мере роста вашей формы. Для небольших форм, таких как login, это, вероятно, хорошо, но когда вы создаете формы профиля пользователя, вам может понадобиться запросить много информации! Должны ли мы вызывать наш хук снова и снова?
Всякий раз, когда я сталкиваюсь с подобной ситуацией, я склоняюсь к написанию пользовательского хука, который хранит все мои формы в одном большом фрагменте. Это может выглядеть так:
С помощью хука useFormFields мы можем продолжать добавлять поля, не добавляя сложности нашему компоненту. Мы можем получить доступ ко всем состояниям формы в одном месте, а наш код выглядит аккуратно и лаконично. Конечно, вам, вероятно, понадобится использовать setState для некоторых ситуаций, но для большинства форм приведённый приём подойдёт просто отлично.
Таким образом, обработка состояния отлично работает, и в большинстве случаев это предпочтительный подход в React. Но знаете ли вы, что есть другой способ? Оказывается, браузер по умолчанию обрабатывает внутреннее состояние формы, и мы можем использовать это для упрощения нашего кода!
Вот та же форма, но позволяющая браузеру обрабатывать состояние формы:
Посмотрите как просто! Не использовано ни одного хука, нет установки начального значения, отсутствует обработчик onChange. Самое приятное то, что код все еще работает как и прежде — но как?
Вы могли заметить, что мы делаем что-то немного другое в функции handleSubmit. Мы используем встроенный browser API под названием FormData. FormData — это удобный (и хорошо поддерживаемый) способ получения значений из наших полей ввода!
Мы получаем ссылку на форму в DOM через атрибут target события submit и создаем новый экземпляр класса FormData. Теперь мы можем получить все поля по их атрибуту name, вызвав formData.get («имя поля ввода»).
Таким образом, вам никогда не нужно явно обрабатывать состояние формы. Если вам нужны значения по умолчанию (например, если вы изменяете начальные значения полей из базы данных или локального хранилища), React предоставляет вам удобную опцию defaultValue для этого.
Поскольку формы являются неотъемлемой частью большинства веб-приложений, важно знать, как с ними обращаться. И React предоставляет вам множество способов сделать это.
Для простых форм, которые не требуют тщательной проверки (или которые могут полагаться на элементы управления проверкой формы HTML5), я предлагаю вам просто использовать встроенную обработку состояний, которую DOM предоставляет нам по умолчанию. Есть довольно много вещей, которые вы не можете сделать (например, программно изменить входные значения или проверка в реальном времени), но в самых простых случаях (например, поле поиска или поле входа в систему, как указано выше), вам, вероятно, подойдёт наш альтернативный подход с использованием browser API.
Когда вы выполняете пользовательскую проверку или вам нужен доступ к некоторым данным формы перед её отправкой, вам нужно явно обработать состояние с помощью контролируемых компонентов. Вы можете использовать обычные useStateHooks или создать пользовательский хук, чтобы немного упростить ваш код.
Стоит отметить, что разработчики React рекомендуют использовать контролируемые компоненты (явно обрабатывать состояние компонента) в большинстве случаев — поскольку этот подход дает вам большую гибкость в дальнейшем. На мой взгляд, разработчики часто жертвуют простотой ради гибкости, которая им зачастую не нужна.
Какой бы подход вы не решили использовать, обработка форм в React никогда не была такой простой, как сегодня. Вы можете позволить браузеру обрабатывать простые формы, в то же время явно обрабатывая состояние форм, когда того требует ситуация. Надеюсь, приведённые способы, будут вам полезны и помогут решить задачу, сократив количество строк в вашем коде.
Поля ввода. Текстовые области. Радиокнопки и чекбоксы. Вот некоторые из основных точек взаимодействия, которые мы, как разработчики, имеем с нашими пользователями. Мы размещаем их на сайте, пользователи их заполняют и если нам повезёт, то заполненные формы приходят к нам без ошибок валидации.
Обработка форм является неотъемлемой частью большого количества веб-приложений, и это одна из вещей, которые React делает лучше всего. У вас есть большая свобода для создания и обработки форм. Но есть ли лучший способ сделать это?
Обратите внимание, что во всех этих примерах мы создадим форму входа с электронной почтой и полем пароля, но эти методы можно использовать с большинством типов форм.
Помните о правилах хорошего тона
Хотя это не имеет прямого отношения к обсуждаемой теме, я хочу убедиться, что вы не забыли сделать свои формы понятными для всех. Добавьте теги к своим полям ввода, установите правильные aria-атрибуты для случаев, когда ввод недопустим, и структурируйте ваш контент семантически правильно. Это облегчает использование вашей формы как для пользователей, так и для разработчиков.
Обработка форм с использованием State Hook
Для начала давайте посмотрим, как я обычно работаю с состоянием формы. Я сохраняю все поля как отдельные элементы состояния и обновляю их все по отдельности, что выглядит примерно так:
function LoginForm() {
const [email, setEmail] = React.useState('');
const [password, setPassword] = React.useState('');
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
api.login(email, password);
};
return (
<form onSubmit={handleSubmit}>
<div>
<label htmlFor='email'>Email</label>
<input type='email' id='email' value={email} onChange={(e) => setEmail(e.target.value)} />
</div>
<div>
<label htmlFor='password'>Password</label>
<input type='password' id='password' value={password} onChange={(e) => setPassword(e.target.value)} />
</div>
</form>
);
}
Сначала я создаю две отдельные части состояния — имя пользователя и пароль. Эти две переменные затем передаются в соответствующее поле ввода, определяя значение этого поля. Всякий раз, когда что-то в поле изменяется, мы обязательно обновляем значение состояния, вызывая повторную перерисовку нашего приложения.
Такая обработка форм отлично работает в большинстве случаев и является простой и легкой в использовании и понимании. Тем не менее, довольно утомительно писать такую конструкцию каждый раз.
Создание пользовательского хука
Давайте сделаем небольшой рефакторинг и создадим собственный хук, который немного улучшит наш код:
const useFormField = (initialValue: string = '') => {
const [value, setValue] = React.useState(initialValue);
const onChange = React.useCallback((e: React.ChangeEvent<HTMLInputElement>) => setValue(e.target.value), []);
return { value, onChange };
};
export function LoginForm() {
const emailField = useFormField();
const passwordField = useFormField();
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
api.login(emailField.value, passwordField.value);
};
return (
<form onSubmit={handleSubmit}>
<div>
<label htmlFor='email'>Email</label>
<input type='email' id='email' {...emailField} />
</div>
<div>
<label htmlFor='password'>Password</label>
<input type='password' id='password' {...passwordField} />
</div>
</form>
);
}
Мы создаем пользовательский хук useFormField, который создает для нас обработчик события onChange, а также сохраняет значение в состояние формы. В этом случае мы можем использовать наш хук со всеми полями формы.
Обработка большого количества полей
Недостатком этого подхода является то, что он плохо масштабируется по мере роста вашей формы. Для небольших форм, таких как login, это, вероятно, хорошо, но когда вы создаете формы профиля пользователя, вам может понадобиться запросить много информации! Должны ли мы вызывать наш хук снова и снова?
Всякий раз, когда я сталкиваюсь с подобной ситуацией, я склоняюсь к написанию пользовательского хука, который хранит все мои формы в одном большом фрагменте. Это может выглядеть так:
export function LoginForm() {
const { formFields, createChangeHandler } = useFormFields({
email: '',
password: '',
});
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
api.login(formFields.email, formFields.password);
};
return (
<form onSubmit={handleSubmit}>
<div>
<label htmlFor='email'>Email</label>
<input type='email' id='email' value={formFields.email} onChange={createChangeHandler('email')} />
</div>
<div>
<label htmlFor='password'>Password</label>
<input type='password' id='password' value={formFields.password} onChange={createChangeHandler('password')} />
</div>
</form>
);
}
С помощью хука useFormFields мы можем продолжать добавлять поля, не добавляя сложности нашему компоненту. Мы можем получить доступ ко всем состояниям формы в одном месте, а наш код выглядит аккуратно и лаконично. Конечно, вам, вероятно, понадобится использовать setState для некоторых ситуаций, но для большинства форм приведённый приём подойдёт просто отлично.
Альтернативный подход
Таким образом, обработка состояния отлично работает, и в большинстве случаев это предпочтительный подход в React. Но знаете ли вы, что есть другой способ? Оказывается, браузер по умолчанию обрабатывает внутреннее состояние формы, и мы можем использовать это для упрощения нашего кода!
Вот та же форма, но позволяющая браузеру обрабатывать состояние формы:
export function LoginForm() {
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
const formData = new FormData(e.target as HTMLFormElement);
api.login(formData.get('email'), formData.get('password'));
};
return (
<form onSubmit={handleSubmit}>
<div>
<label htmlFor="email">Email</label>
<input
type="email"
id="email"
name="email"
/>
</div>
<div>
<label htmlFor="password">Password</label>
<input
type="password"
id="password"
name="password"
/>
</div>
<button>Log in</button>
</form>
);
}
Посмотрите как просто! Не использовано ни одного хука, нет установки начального значения, отсутствует обработчик onChange. Самое приятное то, что код все еще работает как и прежде — но как?
Вы могли заметить, что мы делаем что-то немного другое в функции handleSubmit. Мы используем встроенный browser API под названием FormData. FormData — это удобный (и хорошо поддерживаемый) способ получения значений из наших полей ввода!
Мы получаем ссылку на форму в DOM через атрибут target события submit и создаем новый экземпляр класса FormData. Теперь мы можем получить все поля по их атрибуту name, вызвав formData.get («имя поля ввода»).
Таким образом, вам никогда не нужно явно обрабатывать состояние формы. Если вам нужны значения по умолчанию (например, если вы изменяете начальные значения полей из базы данных или локального хранилища), React предоставляет вам удобную опцию defaultValue для этого.
Когда лучше использовать каждый подход
Поскольку формы являются неотъемлемой частью большинства веб-приложений, важно знать, как с ними обращаться. И React предоставляет вам множество способов сделать это.
Для простых форм, которые не требуют тщательной проверки (или которые могут полагаться на элементы управления проверкой формы HTML5), я предлагаю вам просто использовать встроенную обработку состояний, которую DOM предоставляет нам по умолчанию. Есть довольно много вещей, которые вы не можете сделать (например, программно изменить входные значения или проверка в реальном времени), но в самых простых случаях (например, поле поиска или поле входа в систему, как указано выше), вам, вероятно, подойдёт наш альтернативный подход с использованием browser API.
Когда вы выполняете пользовательскую проверку или вам нужен доступ к некоторым данным формы перед её отправкой, вам нужно явно обработать состояние с помощью контролируемых компонентов. Вы можете использовать обычные useStateHooks или создать пользовательский хук, чтобы немного упростить ваш код.
Стоит отметить, что разработчики React рекомендуют использовать контролируемые компоненты (явно обрабатывать состояние компонента) в большинстве случаев — поскольку этот подход дает вам большую гибкость в дальнейшем. На мой взгляд, разработчики часто жертвуют простотой ради гибкости, которая им зачастую не нужна.
Какой бы подход вы не решили использовать, обработка форм в React никогда не была такой простой, как сегодня. Вы можете позволить браузеру обрабатывать простые формы, в то же время явно обрабатывая состояние форм, когда того требует ситуация. Надеюсь, приведённые способы, будут вам полезны и помогут решить задачу, сократив количество строк в вашем коде.