Если вы работаете с большими формами в React, особенно в enterprise-приложениях, то знаете, как быстро управляемые компоненты могут стать узким местом производительности. Каждый keystroke в управляемом поле вызывает вызов setState, что приводит к повторным рендерам и заметной задержке между вводом и отображением текста. Это особенно ощутимо в формах с 20+ полями, вложенными секциями и динамическими элементами.
React Hook Form: Uncontrolled-First подход
React Hook Form (RHF) предлагает альтернативу — подход uncontrolled-first. Вместо того чтобы хранить значения в состоянии React, RHF использует refs и DOM, обновляя состояние только когда это действительно необходимо. Это позволяет существенно сократить количество рендеров и уменьшить задержку ввода. В одном из моих бенчмарков переход на RHF + Zod снизил задержку с ~80ms до <10ms, а количество рендеров с 6 до 1 на каждый keystroke.
Основные принципы работы с RHF
-
Используйте
registerдля нативных input-полей
Это позволяет работать с компонентами в uncontrolled-режиме.ControllerиuseControllerследует использовать только для сторонних управляемых компонентов. -
Стратегическая валидация
ИспользуйтеzodResolverдля единого источника истины, но валидируйте данные только при отправке формы (mode: 'onSubmit') или при потере фокуса (mode: 'onTouched'), а не на каждый keystroke. -
Избегайте глобального
watch
Вызовwatch()на уровне всей формы подписывает все поля на изменения, что приводит к множественным рендерам. Вместо этого используйтеuseFormStateдля флагов уровня формы (например,isSubmitting) внутри дочернего компонента иuseWatchдля конкретных полей. -
Минимизируйте вызовы
setValueиuseController
Пакетируйте программные обновления и избегайте записи значений на каждое событие ввода. -
Профилируйте рендеры
Используйте React DevTools Profiler и функцию “Highlight updates” для подтверждения уменьшения количества рендеров после внесения изменений.
Пример минимальной настройки формы
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
const schema = z.object({
email: z.string().email(),
// ... другие поля
});
export function MyForm() {
const { register, handleSubmit, control } = useForm({
resolver: zodResolver(schema),
mode: 'onSubmit', // или 'onTouched'
defaultValues: { email: '', /* ... */ },
});
return (
<form onSubmit={handleSubmit(data => console.log(data))}>
<input type="email" {...register('email')} />
{/* другие registered inputs */}
<button type="submit">Submit</button>
</form>
);
}
Этот подход делает ввод текста максимально дешевым — RHF обновляет внутренние refs и DOM, но не вызывает повторный рендер всей формы для каждого символа.
Работа с управляемыми компонентами
Если вам необходимо использовать управляемый компонент (например, React-Select или Material UI TextField), оберните только это поле в Controller. Это ограничит повторные рендеры поддеревом этого компонента.
import { Controller } from 'react-hook-form';
<Controller
name="country"
control={control}
render={({ field }) => (
<ReactSelect {...field} options={countryOptions} />
)}
/>
Оптимизация валидации
Валидация не бесплатна. Если вы используете mode: 'onChange или запускаете resolver на каждый keystroke, это вызовет повторную валидацию (и потенциально повторные рендеры) на каждый ввод. Вместо этого:
- По умолчанию используйте
onSubmitилиonTouchedдля больших форм. - Если поле требует мгновенной обратной связи (например, проверка сложности пароля), запускайте валидацию только для этого поля с помощью
trigger('password')изonChangecallback. - Используйте
zodResolverдля централизации правил валидации и их повторного использования на сервере.
Профилирование и бенчмарки
В моем бенчмарке форма с 20+ полями и вложенными секциями показала следующие результаты:
- Задержка ввода: ~80ms → <10ms
- Количество рендеров на keystroke: 6 → 1
- Воспринимаемая скорость: мгновенная
Эти цифры консервативны для реальных UI с тяжелыми компонентами и условными секциями — небольшие изменения в настройке могут дать значительный прирост производительности.
Когда использовать RHF + Zod
Подход uncontrolled-first с RHF и Zod — это практичное и измеримое улучшение для enterprise-форм. Однако есть и trade-offs:
- Необходимо быть внимательным к местам подписки (
useWatch/useFormState). - Управляемые компоненты все еще вызывают повторные рендеры (но только в своем поддереве).
- Добавление
zodResolverувеличивает размер бандла; используйте его для форм, которые требуют сложной валидации или синхронизации с сервером.
Если ваши формы начинают тормозить при увеличении масштаба, этот подход — один из самых эффективных способов улучшить производительность. Начните с чеклиста, профилируйте и перемещайте подписки вниз — результаты будут заметны сразу.
Какие узкие места вы обнаружили в своих формах и как их устранили? Делитесь примерами из практики — иногда небольшие изменения могут дать значительный прирост.
Источник: https://dev.to/nainikmehta/uncontrolled-first-react-forms-react-hook-form-zod-1653