Контролируемые компоненты
▸Определение
Контролируемые компоненты — инпуты, значение которых управляется через state React. При каждом keystroke обновляется состояние и ре-рендерится инпут.
▸Пример
1function ControlledInput() {2 const [value, setValue] = useState('');34 return (5 <input6 type="text"7 value={value}8 onChange={(e) => setValue(e.target.value)}9 />10 );11}
▸Преимущества
▸Недостатки
Неконтролируемые компоненты
▸Определение
Неконтролируемые компоненты хранят значение в DOM, а не в state React. Используют ref для доступа к значению.
▸Пример
1function UncontrolledInput() {2 const inputRef = useRef(null);34 const handleSubmit = () => {5 console.log(inputRef.current.value);6 };78 return (9 <input type="text" ref={inputRef} />10 );11}
▸Преимущества
▸Недостатки
Сравнение подходов
▸Когда использовать контролируемые
▸Когда использовать неконтролируемые
React Hook Form
▸Установка
1npm install react-hook-form
▸Базовое использование
1import { useForm } from 'react-hook-form';23function SignupForm() {4 const { register, handleSubmit, formState: { errors } } = useForm();56 const onSubmit = (data) => console.log(data);78 return (9 <form onSubmit={handleSubmit(onSubmit)}>10 <input {...register('email', { required: true })} />11 {errors.email && <span>Обязательное поле</span>}1213 <input {...register('password', { minLength: 6 })} />14 {errors.password && <span>Минимум 6 символов</span>}1516 <button type="submit">Зарегистрироваться</button>17 </form>18 );19}
▸Валидация
1const { register } = useForm({2 mode: 'onChange', // валидация при каждом изменении3});45<input6 {...register('email', {7 required: 'Email обязателен',8 pattern: {9 value: /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i,10 message: 'Некорректный email',11 },12 })}13/>
▸Controller для сложных компонентов
1import { Controller } from 'react-hook-form';23<Controller4 name="country"5 control={control}6 render={({ field }) => (7 <Select {...field} options={countries} />8 )}9/>
Formik
▸Установка
1npm install formik
▸Базовое использование
1import { Formik, Form, Field, ErrorMessage } from 'formik';2import * as Yup from 'yup';34const SignupSchema = Yup.object().shape({5 email: Yup.string().email('Некорректный email').required('Обязательно'),6 password: Yup.string().min(6, 'Минимум 6 символов').required('Обязательно'),7});89function SignupForm() {10 return (11 <Formik12 initialValues={{ email: '', password: '' }}13 validationSchema={SignupSchema}14 onSubmit={(values) => console.log(values)}15 >16 {({ isSubmitting }) => (17 <Form>18 <Field type="email" name="email" />19 <ErrorMessage name="email" component="div" />2021 <Field type="password" name="password" />22 <ErrorMessage name="password" component="div" />2324 <button type="submit" disabled={isSubmitting}>25 Отправить26 </button>27 </Form>28 )}29 </Formik>30 );31}
React Hook Form vs Formik
▸Производительность
React Hook Form использует uncontrolled подход с ref, что делает его быстрее. Formik контролирует каждое поле через state.
▸Bundle size
React Hook Form: ~9 KB. Formik: ~44 KB. Значительная разница.
▸TypeScript
React Hook Form имеет лучшую поддержку TypeScript. Formik требует больше boilerplate.
Валидация
▸Yup
1const schema = Yup.object().shape({2 name: Yup.string().required(),3 age: Yup.number().min(18).required(),4});
▸Zod
1import { z } from 'zod';23const schema = z.object({4 name: z.string().min(1),5 age: z.number().min(18),6});78// Интеграция с React Hook Form9const { register } = useForm({10 resolver: zodResolver(schema),11});
Best Practices
▸Не используйте formik для новых проектов
React Hook Form — современный выбор. Formik устаревает.
▸Валидируйте на клиенте и сервере
Клиентская валидация — UX, серверная — безопасность.
▸Показывайте ошибки рядом с полями
Не группируйте ошибки в одном месте. Пользователь должен видеть ошибку возле проблемного поля.
Заключение
Контролируемые компоненты — стандарт React. Для сложных форм используйте React Hook Form или Formik. React Hook Form — предпочтительный выбор в 2024 году благодаря производительности и TypeScript-поддержке.