React

Формы: controlled и uncontrolled

Форма регистрации, поле поиска, фильтры в каталоге, настройки профиля - ввод данных есть почти в каждом приложении, и именно на формах новички спотыкаются чаще всего. Главный вопрос: кто хранит значение поля - сам DOM или React? От ответа зависит всё: можно ли валидировать ввод на лету, отключать кнопку отправки, форматировать телефон по мере набора. React предлагает два подхода - controlled и uncontrolled - и понимание разницы между ними отделяет форму, которая просто собирает данные, от формы, которая живёт и реагирует на каждый символ.

  • Живая валидация (подсветка ошибки сразу при вводе), индикатор силы пароля, счётчик символов - всё это требует контролируемых полей
  • Поиск с подсказками по мере набора в Google или GitHub - классический controlled-инпут, читающий значение на каждом onChange
  • Простые формы с одной кнопкой отправки часто делают uncontrolled через ref - меньше ре-рендеров и кода
  • React 19 добавил form actions и хуки useActionState, useFormStatus - новый способ обрабатывать отправку форм, в том числе на сервере

Предварительные знания

  • События и обработчики: onChange, аргумент события, event.target.value, preventDefault
  • useState: хранение и иммутабельное обновление значения, в том числе объекта
  • Идея односторонего потока данных: значение спускается вниз, событие идёт наверх

Контролируемые поля: value и onChange

В контролируемом поле источник правды - состояние React, а не DOM. Полю задают два атрибута: value привязывает его к переменной состояния, а onChange при каждом изменении пишет новое значение в состояние. Получается замкнутый цикл: пользователь печатает, onChange обновляет состояние, состояние возвращается в value, поле перерисовывается. DOM никогда не хранит значение сам - он лишь отображает то, что в состоянии.

Раз значение живёт в состоянии, с ним можно делать что угодно прямо во время ввода: считать длину, валидировать, форматировать, отключать кнопку. В примере счётчик символов и блокировка кнопки работают на каждый набранный символ, потому что состояние - единый источник правды для всего этого.

Если задать value без onChange, поле станет доступным только для чтения: пользователь не сможет в нём печатать, а React в режиме разработки выдаст предупреждение. Контролируемое поле требует обоих атрибутов вместе.

Где хранится значение контролируемого поля?

Неконтролируемые поля: defaultValue и ref

В неконтролируемом поле значение хранит сам DOM, как в обычном HTML. React не привязывается к каждому нажатию клавиши. Начальное значение задают через defaultValue (не value), а прочитать итог можно при необходимости - например, при сабмите - через ref, ссылку на DOM-узел. Такой подход проще и вызывает меньше ре-рендеров, потому что состояние не обновляется на каждый символ.

  • Controlled — Значение в состоянии (value + onChange). Нужен для живой валидации, форматирования, зависимой логики. Ре-рендер на каждый ввод
  • Uncontrolled — Значение в DOM (defaultValue + ref). Проще и легче для разовых форм, где значение нужно только при отправке

Официальная рекомендация React - предпочитать контролируемые поля для большинства случаев, потому что они дают полный контроль над значением. Неконтролируемые уместны для простых форм, интеграции со сторонним кодом и файловых input type=file, значение которых задать программно нельзя.

Когда уместно неконтролируемое поле с ref вместо контролируемого?

Много полей и form actions в React 19

Заводить отдельный useState на каждое поле большой формы утомительно. Удобнее хранить все значения в одном объекте состояния и обновлять его одним обработчиком. Ключом служит атрибут name поля, а обновление делают иммутабельно через computed property name: создаётся новый объект, в котором переписано только изменившееся поле.

React 19 добавил новый, более декларативный путь для отправки форм - form actions. Атрибут action у тега form принимает функцию, которой React передаёт FormData со всеми полями. Хук useActionState управляет состоянием отправки и результатом, а useFormStatus даёт дочерним компонентам знать, идёт ли отправка прямо сейчас. Этот же механизм работает и с серверными функциями, объединяя клиент и сервер.

Form actions не отменяют контролируемые поля - это дополнительный инструмент, особенно сильный с серверными функциями и прогрессивным улучшением. Для живой валидации на лету контролируемое состояние по-прежнему остаётся основным подходом.

Как удобнее всего обработать множество полей одной формы одним обработчиком?

Связь с другими темами

Формы - концентрат всего, что было в модуле: состояние, события, поток данных:

  • События и обработчики — onChange и чтение event.target.value - основа любого контролируемого поля
  • Подъём состояния — Состояние формы поднимают к родителю, когда от него зависят соседние компоненты
  • useState — Значения контролируемых полей хранятся и обновляются через состояние

Итог

  • Контролируемое поле: значение хранит React (value + onChange). Источник правды - состояние, поле всегда отражает его
  • Неконтролируемое поле: значение хранит DOM (defaultValue), а React читает его при необходимости через ref
  • Controlled нужен, когда значение участвует в логике: валидация на лету, форматирование, отключение кнопки
  • Uncontrolled проще и легче для разовых форм, где значение нужно только в момент отправки
  • Множество полей удобно вести одним обработчиком через атрибут name и иммутабельное обновление объекта
  • React 19 добавил form actions и useActionState - декларативный способ обработки отправки, в том числе серверной

Связанные уроки

  • rc-07-events-handlers — Контролируемое поле целиком строится на onChange и аргументе события из урока про события
  • rc-06-usestate — Значение контролируемого поля хранится в useState, поэтому состояние нужно понимать заранее
  • rc-09-lifting-state — Состояние формы часто поднимают к родителю, чтобы несколько полей и кнопок видели одни данные
Формы: controlled и uncontrolled

0

1

Войти