Главная » Статьи » Использование форм в React

Использование форм в React

Использование форм в React

От автора: независимо от того, какое приложение вы пишете, велика вероятность, что вам понадобится хотя бы одна форма. Формы в React часто вызывают затруднения, поскольку содержат детальный и шаблонный код. Давайте посмотрим, как создавать формы в React с меньшими усилиями.

В этой статье мы сосредоточимся на использовании простого React без библиотек. Вы узнаете, как на самом деле работают формы, и сможете уверенно создавать их самостоятельно. И если позже вы решите добавить библиотеку форм, вы узнаете, как они работают под капотом.

Мы собираемся познакомится с такими вопросами:

Как создавать формы React без установки каких-либо библиотек

Два стиля входных данных в формах React

Когда использовать контролируемый или неконтролируемый ввод

Простой способ получить значения из неконтролируемого ввода

Как создавать формы с помощью Plain React

Давайте начнем. Мы собираемся создать простую контактную форму. Рассмотрим автономный компонент ContactForm который отображает форму:

function ContactForm() { return ( <form> <div> <label htmlFor="name">Name</label> <input id="name" type="text" /> </div> <div> <label htmlFor="email">Email</label> <input id="email" type="email" /> </div> <div> <label htmlFor="message">Message</label> <textarea id="message" /> </div> <button type="submit">Submit</button> </form> );
}

Вам не нужно устанавливать дополнительных библиотек. React имеет встроенную поддержку форм, потому что HTML и DOM имеют встроенную поддержку форм. В конце концов, React отрисовывает узлы DOM.

Фактически, для малых форм вам, вероятно, вообще не нужна библиотека форм. Что-то вроде Formik или response-hook-form будет излишним, если вам нужна простая форма.

Здесь еще нет состояния, и мы не отвечаем на отправку формы, но этот компонент уже будет отображать форму, с которой вы можете взаимодействовать. (Если вы отправите его, страница перезагрузится, потому что отправка по-прежнему обрабатывается браузером по умолчанию).

React Forms против HTML-форм

Если вы работали с формами в обычном HTML, многое из этого, вероятно, покажется вам знакомым. Там есть тег form, и label для ввода, как если бы вы писали в HTML.

У каждой label есть свойство htmlFor которое соответствует значению id на входе. (Есть одно отличие: React использует htmlFor вместо for.)

Если вы не знакомы с HTML, просто знайте, что React не придумал ничего нового! Совйства в React является довольно ограничены, и работа формы заимствована из HTML и DOM.

Два типа ввода: контролируемый и неконтролируемый

Ввод данных в React может быть одного из двух типов: контролируемый и неконтролируемый. Неконтролируемый ввод проще. Это самый близкий к обычному HTML-вводу. React размещает его на странице, а браузер отслеживает все остальное. Когда вам нужно получить доступ к входному значению, React предоставляет способ сделать это. Неконтролируемые вводы требуют меньше кода, но затрудняют выполнение определенных действий.

С управляемым вводом ВЫ явно контролируете значение, отображаемое на вводе. Вы должны написать код, чтобы реагировать на нажатия клавиш, где-то хранить текущее значение и передавать это значение обратно на ввод для отображения. Это цикл обратной связи с вашим кодом. Их подключение требует больше ручной работы, но они обеспечивают максимальный контроль.

Давайте посмотрим на эти два стиля на практике, применив их к нашей контактной форме.

Контролируемый ввод

С управляемым вводом вы пишете код для явного управления значением. Вам нужно будет создать состояние для его хранения, обновить это состояние при изменении значения и явно указать входным данным, какое значение отображать.

Чтобы обновить нашу контактную форму для использования контролируемого ввода, нам нужно добавить несколько вещей, выделенных здесь:

function ContactForm() { const [name, setName] = React.useState(''); const [email, setEmail] = React.useState(''); const [message, setMessage] = React.useState(''); function handleSubmit(event) { event.preventDefault(); console.log('name:', name); console.log('email:', email); console.log('message:', message); } return ( <form onSubmit={handleSubmit}> <div> <label htmlFor="name">Name</label> <input id="name" type="text" value={name} onChange={(e) => setName(e.target.value)} /> </div> <div> <label htmlFor="email">Email</label> <input id="email" type="email" value={email} onChange={(e) => setEmail(e.target.value)} /> </div> <div> <label htmlFor="message">Message</label> <textarea id="message" value={message} onChange={(e) => setMessage(e.target.value)} /> </div> <button type="submit">Submit</button> </form> );
}

Мы добавили 3 вызова useState для создания 3 переменных для хранения значений входных данных. Они изначально пустые. Каждый input получил пару новых реквизитов.

Value сообщает, что отображать. Здесь мы передаем значение из соответствующей переменной состояния.

onChange является функцией и вызывается, когда пользователь изменяет ввод. Он получает событие (обычно называемое e или event, но вы можете назвать его как угодно), и мы берем текущее значение ввода ( e.target.value) и сохраняем его в состоянии.

При каждом нажатии клавиши вызывается наш onChange, и мы явно устанавливаем setWhatever, который повторно отображает всю ContactForm с новым значением. Это означает, что при каждом нажатии клавиши компонент будет повторно отображать всю форму.

Для малых форм это нормально. И это на самом деле нормально. Визуализация выполняется быстро. Рендеринг 3, 5 или 10 вводов при каждом нажатии клавиш не замедлит работу приложения заметно.

Если у вас есть форма с множеством вводов, этот повторный рендеринг может иметь значение, особенно на более медленных устройствах. На этом этапе вам может потребоваться изучить оптимизацию, чтобы ограничить повторную визуализацию только теми входными данными, которые изменились.

Или подумайте, как вы могли бы упростить форму, чтобы одновременно отображалось меньше данных ввода. Если React недоволен повторным рендерингом 100 вводов при каждом нажатии клавиши, я полагаю, что ваши пользователи тоже не очень довольны просмотром 100 вводов на странице.

Альтернатива…

Неконтролируемый ввод

Если вы ничего не сделаете, кроме передачи input в вашей функции рендеринга, этот ввод будет неконтролируемым. Вы говорите React визуализировать ввод, а браузер делает все остальное.

Неконтролируемый ввод управляет своим значением. Как и в случае с простой HTML-формой, значение сохраняется во входном узле DOM. Нет необходимости отслеживать это вручную.

В первом примере кода на этой странице все вводы были неконтролируемыми, потому что мы не передавали свойство value, которое сообщала бы нам, какое значение отображать.

Но если мы не отслеживаем значение … как мы можем определить, каково оно? Вот где пригодятся «ref».

Что такое «ref»?

React берет ваш JSX и создает фактическую модель DOM, которую отображает браузер. Ref связывает эти два представления вместе, позволяя вашему компоненту React получить доступ к узлам DOM, которые его представляют. Ref содержит ссылку на узел DOM.

Вот почему это важно: JSX, который вы пишете, — это просто описание страницы, которую вы хотите создать. Что вам действительно нужно, так это базовая модель DOM input, чтобы вы могли извлечь значение.

Итак, чтобы получить значение из неконтролируемого ввода, вам нужна ссылка на него, которую мы получаем, назначая свойству ref. Затем вы можете прочитать значение при отправке формы (или, когда захотите!).

Давайте добавим ссылки в input нашей контактной формы, основываясь на примере нашей «простой формы»:

function ContactForm() { const nameRef = React.useRef(); const emailRef = React.useRef(); const messageRef = React.useRef(); return ( <form> <div> <label htmlFor="name">Name</label> <input id="name" type="text" ref={nameRef} /> </div> <div> <label htmlFor="email">Email</label> <input id="email" type="email" ref={emailRef} /> </div> <div> <label htmlFor="message">Message</label> <textarea id="message" ref={messageRef} /> </div> <button type="submit">Submit</button> </form> );
}

Здесь мы сделали пару вещей:

создал 3 ссылки с помощью хука useRef

привязали ссылки к входным данным с помощью свойства ref

Когда компонент отрисовывается впервые, React настроит ссылки. nameRef.current будет ссылаться на name узела DOM, emailRef.current будет ссылаться на входное сообщение электронной почты и так далее.

Эти ссылки содержат те же значения, что и те, которые вы получили бы, если бы запустили document.querySelector(‘input[id=name]‘) в консоли браузера. Это необработанная входная нода браузера; React просто возвращает ее вам.

Последний блок головоломки — как получить значения из входных данных. Неконтролируемые вводы — лучший выбор, когда вам нужно сделать что-то со значением только в определенное время, например, при отправке формы. (Если вам нужно проверять / проверять / преобразовывать значение при каждом нажатии клавиши, используйте контролируемый ввод)

Мы можем создать функцию для обработки отправки формы и распечатать значения:

function ContactForm() { const nameRef = React.useRef(); const emailRef = React.useRef(); const messageRef = React.useRef(); function handleSubmit(event) { event.preventDefault(); console.log('name:', nameRef.current.value); console.log('email:', emailRef.current.value); console.log('message:', messageRef.current.value); } return ( <form onSubmit={handleSubmit}> <div> <label htmlFor="name">Name</label> <input id="name" type="text" ref={nameRef} /> </div> <div> <label htmlFor="email">Email</label> <input id="email" type="email" ref={emailRef} /> </div> <div> <label htmlFor="message">Message</label> <textarea id="message" ref={messageRef} /> </div> <button type="submit">Submit</button> </form> );
}

Функция handleSubmit может делать с этими значениями все, что вам нужно: проверять их, асинхронно отправлять их на сервер и т. д. Обратите внимание, на вызов event.preventDefault(). Без этого отправка формы обновит страницу.

Контролируемое или неконтролируемое: что использовать?

Не будем подробно останавливаться на некоторых плюсах и минусах каждого стиля ввода, чтобы вы могли решить, какой из них вы хотите использовать.

(Возможно, вы слышали, что контролируемый ввод — это «лучшая практика», что, конечно, подразумевает, что неконтролируемый ввод — нет! Я вернусь к этому ближе к концу.)

Когда и зачем использовать контролируемые вводы

Из двух стилей контролируемый ввод является более «реактивным» способом выполнения действий, когда пользовательский интерфейс отражает состояние. Изменяя состояние, вы меняете пользовательский интерфейс. Если вы не измените состояние, пользовательский интерфейс останется прежним.

Это делает контролируемые входы идеальными для таких вещей, как:

Мгновенная проверка формы при каждом нажатии клавиши: полезно, если вы хотите, например, сделать кнопку «Отправить» отключенной, пока все значения не станут валидными.

Обработка форматированного ввода, например поля номера кредитной карты, или предотвращение ввода определенных символов.

Синхронизация нескольких вводов друг с другом, если они основаны на одних и тех же данных

Решение остается за вами, дорогой разработчик. Хотите игнорировать какой-то странный символ, введенный пользователем? Легко, просто убери это.

function EmailField() { const [email, setEmail] = useState(''); const handleChange = e => { // no exclamations allowed! setEmail(e.target.value.replace(/!/g, '')); } return ( <div> <label htmlFor="email">Email address</label> <input id="email" value={email} onChange={handleChange} /> </div> );
}

Есть множество вариантов использования, когда вы хотите реагировать на каждое нажатие клавиши и как-то его обрабатывать. Для этого подходят контролируемые вводы. Но есть и минусы.

Контролируемые вводы более сложны

Как мы уже видели, вам нужно вручную управлять значением ввода, что означает, что вам нужно (а) состояние для его хранения и (б) функция обработчика изменений, и они нужны вам для каждого ввода. Вы можете частично обойти эту проблему, объединив входные данные в один объект состояния:

function MultipleInputs() { const [values, setValues] = useState({ email: '', name: '' }); const handleChange = e => { setValues(oldValues => ({ ...oldValues, [e.target.name]: e.target.value })); } return ( <> <div> <label htmlFor="email">Email address</label> <input id="email" name="email" value={values.email} onChange={handleChange} /> </div> <div> <label htmlFor="name">Full Name</label> <input id="name" name="name" value={values.name} onChange={handleChange} /> </div> </> );
}

Такой шаблонный макет — одна из причин, по которой библиотеки форм React так популярны, но опять же, если у вас есть 2 или 3 ввода на странице, я бы сказал, что экономия нескольких строк не стоит добавления библиотеки форм.

Повторный рендеринг контролируемых вводов при каждом нажатии клавиши

Каждый раз, когда вы нажимаете клавишу, React вызывает функцию в свойстве onChange, которая устанавливает состояние. Установка состояния приводит к повторному рендерингу компонента и его дочерних элементов (если они еще не оптимизированы с помощью React.memo или PureComponent).

В основном это нормально. Визуализация выполняется быстро. Для малых и средних форм вы, вероятно, даже не заметите. И дело не в том, что рендеринг ничтожно маленького размера input происходит медленно… но это может быть проблемой в целом.

По мере увеличения количества входных данных — или если в вашей форме есть дочерние компоненты, которые дорого отрисовывать, — нажатия клавиш могут начать ощущаться с заметной задержкой. Этот порог еще ниже на мобильных устройствах. Это может стать проблемой.

Если вы начинаете подозревать эту проблему в своем приложении, запустите Profiler в React Developer Tools и выполните измерения, нажимая некоторые клавиши. Он скажет вам, какие компоненты замедляют работу.

Неконтролируемые входные данные не обрабатываются повторно

Важным аргументом в пользу использования неконтролируемого ввода является то, что браузер позаботится обо всем. Вам не нужно обновлять состояние, а это значит, что вам не нужно повторно рендерить. Каждое нажатие клавиши обходит React и переходит прямо в браузер.

Ввод буквы ‘a’ в форму с 300 входами приведет к повторному рендерингу ровно ноль раз, что означает, что React может сидеть сложа руки и ничего не делать. Ничего не делать очень эффективно.

У неконтролируемых вводов может быть еще меньше шаблонов!

Ранее мы рассмотрели, как создавать ссылки на входные данные, используя useRef и передавать их в качестве свойства ref.

Фактически вы можете пойти дальше и полностью удалить ссылки, воспользовавшись тем фактом, что форма знает о своих собственных входных данных.

function NoRefsForm() { const handleSubmit = e => { e.preventDefault(); const form = e.target; console.log('email', form.email, form.elements.email); console.log('name', form.name, form.elements.name); } return ( <form onSubmit={handleSubmit}> <div> <label htmlFor="email">Email address</label> <input id="email" name="email" /> </div> <div> <label htmlFor="name">Full Name</label> <input id="name" name="name" /> </div> <button type="submit">Submit</button> </form> );
}

Input — это свойства самой формы, названные по их id и name. Они также доступны по адресу form.elements:

function App() { const handleSubmit = (e) => { e.preventDefault(); const form = e.target; console.log( form.email, form.elements.email, form.userEmail, form.elements.userEmail); }; return ( <form onSubmit={handleSubmit}> <div> <label htmlFor="userEmail">Email address</label> <input id="userEmail" name="email" /> </div> <button type="submit">Submit</button> </form> );
}

Это напечатает один и тот же ввод 4 раза:

<input id="userEmail" name="email"></input>
<input id="userEmail" name="email"></input>
<input id="userEmail" name="email"></input>
<input id="userEmail" name="email"></input>

Таким образом, мы можем исключить избыточное свойство name на входе, если оно нам больше не нужно. (нам нужно сохранить id, потому что htmlFor ссылается на него)

Массив form.elements полезен, если вам нужно перебрать каждый ввод, как если у вас есть куча динамически генерируемых вводов.

Доступные Label для форм

Каждый input должен иметь label. Input без label создает проблемы для программ чтения с экрана, что создает проблемы для людей… а текст-заполнитель, к сожалению, не помогает. Label можно делать двумя способами:

label рядом с input

Задайте для input id и label соответствующий htmlFor, и поместите элементы рядом. Порядок не имеет значения, пока идентификаторы совпадают.

<label htmlFor="wat">Email address</label>
<input id="wat" name="email" />

input внутри label

Если вы поместите input в label, вам не нужны id и htmlFor. Однако вам понадобится способ ссылаться на ввод, поэтому укажите ему id или name.

<label> Email Address <input type="email" name="email" />
</label>

Если вам нужно больше контроля над стилем текста, вы можете обернуть его в span.

Визуально скрыт, но все же доступен

При необходимости вы можете скрыть label с помощью CSS. Большинство крупных CSS-фреймворков имеют класс только для чтения с экрана, который часто sr-only, скрывает label таким образом, чтобы программы чтения с экрана могли ее прочитать. Вот общая реализация sr-only.

Что хорошо в label, так это то, что после того, как вы правильно их связали, браузер будет переводить клики по ярлыку как клики по полю ввода. Это наиболее заметно с переключателями — когда label установлена правильно, клик по тексту выберет нужный элемент, но в противном случае он вас проигнорирует.

Уменьшить форму Boilerplate с небольшими компонентами

Итак, вы добавили свои label, но вывод становятся длиннее и повторяется…

<div> <label htmlFor="email">Email Address</label> <input name="email" id="email">
</div>

Однако вы можете легко переместить это в компонент!

function Input({ name, label }) { return ( <div> <label htmlFor={name}>{label}</label> <input name={name} id={name}> </div> );
}

Теперь каждый ввод снова прост.

<input name="email" label="Email Address"/>

И если вы используете неконтролируемые вводы, вы все равно можете использовать чтения значений из формы, никаких ссылок или состояния не требуется.

Лучшая практика — использовать контролируемые входы?

На момент написания этой статьи в документации React есть рекомендации по входным данным: В большинстве случаев мы рекомендуем использовать контролируемые компоненты для реализации форм. В управляемом компоненте данные формы обрабатываются компонентом React. Альтернативой являются неконтролируемые компоненты, где данные формы обрабатываются самой DOM.

Далее они говорят, что простым выходом является использование неконтролируемого ввода: Также может быть немного меньше кода, если вы хотите, чтобы рендеринг работал быстро. В противном случае обычно следует использовать контролируемые компоненты.

В документации нет точного объяснения аргументов, но я предполагаю, что их рекомендация проистекает из того факта, что контролируемые входные данные тесно связаны с подходом управляемого состояния, что является единственной причиной существования React. Неконтролируемые вводы затем рассматриваются как «аварийный выход», когда подход управляемого состояния не работает по какой-либо причине.

Некоторое время я соглашался с этим мнением, но у меня начинаются сомнения. Я прихожу к мысли, что неконтролируемый ввод может быть лучшим вариантом по умолчанию.

Если неконтролируемый ввод работает в вашем случае, используйте его! Он проще и быстрее. Не думаю, что я одинок в этом. Популярная библиотека response-hook-form использует неконтролируемые вводы под капотом, чтобы ускорить работу. И я видел, как некоторые идейные лидеры React спрашивали, почему мы не используем чаще неконтролируемые вводы. Может, пора задуматься!

Являются ли неконтролируемые вводы антипаттерном?

Неконтролируемые вводы — это такая же функция, как и любая другая, и они имеют некоторые компромиссы (о которых мы говорили выше), но они не являются антипаттерном. В React есть такие антипаттерны, как:

изменяющееся состояние вместо использования неизменяемости

дублирование значений из реквизита в состояние и попытка их синхронизации

выполнение побочных эффектов в теле функции компонента, а не в хуке useEffect

Иногда кажется, что эти вещи работают нормально, но в конечном итоге это неправильный способ, и в будущем они вызовут ошибки.

Неконтролируемые вводы сегодня немного необычны, но их использование не означает «сделать что-то неправильно». Это вопрос выбора правильного инструмента для работы. Если вы понимаете их ограничения и свой вариант использования, то можете быть вполне уверены в своем выборе.

Создавайте формы! Надеюсь, этот обзор форм в React был вам полезен! Я мог бы рассказать гораздо больше, но, честно говоря, это было уже слишком долго.

Автор: Dave Ceddia

Источник: daveceddia.com

Редакция: Команда webformyself.

Читайте нас в Telegram, VK, Яндекс.Дзен