Оптимизация форм
Формы с большим количеством полей могут тормозить. Разберём приёмы оптимизации.
Проблема ререндеров
Каждый ввод символа вызывает setState, что перерисовывает весь компонент и его детей:
function Form() {
const [values, setValues] = useState({ a: '', b: '', c: '' });
function handleChange(e) {
setValues((v) => ({ ...v, [e.target.name]: e.target.value }));
}
return (
<>
<input name="a" value={values.a} onChange={handleChange} />
<input name="b" value={values.b} onChange={handleChange} />
<input name="c" value={values.c} onChange={handleChange} />
</>
);
}
При вводе в поле a перерисовываются все три поля.
Решение 1: отдельные компоненты
Вынеси поля в компоненты и мемоизируй:
const Field = memo(function Field({ name, value, onChange }) {
return <input name={name} value={value} onChange={onChange} />;
});
Решение 2: React Hook Form
React Hook Form использует неконтролируемые поля — ререндеров почти нет:
const { register, handleSubmit } = useForm();
<form onSubmit={handleSubmit(onSubmit)}>
<input {...register('a')} />
<input {...register('b')} />
<input {...register('c')} />
</form>
Решение 3: useRef для полей
Если значение нужно только при отправке — не храни в стейте:
const refs = {
a: useRef<HTMLInputElement>(null),
b: useRef<HTMLInputElement>(null),
};
function handleSubmit(e: React.FormEvent) {
e.preventDefault();
console.log(refs.a.current?.value, refs.b.current?.value);
}
Мемоизация обработчиков
useCallback для обработчиков, передаваемых в мемоизированные дочерние компоненты:
const handleChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
setValues((v) => ({ ...v, [e.target.name]: e.target.value }));
}, []);
Не создавай объекты в пропсах
// Плохо: новый объект при каждом рендере
<Input config={{ type: 'text' }} />
// Хорошо: константа
const config = { type: 'text' };
<Input config={config} />
Debounce для поиска
Если поле запускает поиск — используй debounce:
function useDebounce<T>(value: T, delay: number): T {
const [debounced, setDebounced] = useState(value);
useEffect(() => {
const id = setTimeout(() => setDebounced(value), delay);
return () => clearTimeout(id);
}, [value, delay]);
return debounced;
}
const [query, setQuery] = useState('');
const debouncedQuery = useDebounce(query, 500);
useEffect(() => {
if (debouncedQuery) search(debouncedQuery);
}, [debouncedQuery]);
Ленивая валидация
Не валидируй на каждый ввод, если это дорого. Валидируй при blur или отправке.
<input onBlur={validate} />
Виртуализация
Если полей сотни — используй виртуализацию (react-window, @tanstack/react-virtual). Рендерятся только видимые поля.
Разделение на шаги
Форма-мастер: каждый шаг — отдельный компонент. Меньше полей одновременно — меньше ререндеров.
function Wizard() {
const [step, setStep] = useState(1);
return (
<>
{step === 1 && <Step1 onNext={() => setStep(2)} />}
{step === 2 && <Step2 onNext={() => setStep(3)} />}
</>
);
}
Мемоизация вычислений
Если в форме есть дорогие вычисления (например, валидация сложной схемы) — useMemo:
const isValid = useMemo(() => validate(values), [values]);
Итоги
- Контролируемые поля ререндерят всё
- React Hook Form уменьшает ререндеры
memo,useCallback,useMemoпомогают- Debounce для поиска
- Ленивая валидация
- Виртуализация для больших форм
- Форма-мастер — пошагово