useState, useRef, useEffect)Уявіть, що ви розробляєте складну форму реєстрації з 20+ полями: особисті дані, адреса, налаштування безпеки, преференції. При використанні традиційного підходу з useState кожне натискання клавіші викликає ре-рендер всього компонента. Для 20 полів та активного користувача це означає сотні непотрібних рендерів за хвилину.
React Hook Form вирішує цю фундаментальну проблему через інноваційний підхід, заснований на uncontrolled inputs та розумному управлінні ref-ами.
Мінімальні ре-рендери
Малий розмір бандлу
Продуктивність
DX та інтеграція
Перед тим, як заглибитися в React Hook Form, критично важливо зрозуміти різницю між двома підходами до роботи з формами.
Controlled component — це інпут, чиє значення повністю контролюється React state:
import { useState } from 'react'
function ControlledForm() {
const [email, setEmail] = useState('')
// Кожне натискання клавіші:
// 1. Викликає setEmail
// 2. Оновлює state
// 3. Ре-рендерить компонент
// 4. React порівнює Virtual DOM
// 5. Оновлює реальний DOM (якщо потрібно)
return (
<input
value={email} // Джерело істини: React state
onChange={(e) => setEmail(e.target.value)}
/>
)
}
Життєвий цикл зміни значення:
Переваги controlled:
Недоліки controlled:
Uncontrolled component — це інпут, чиє значення зберігається в самому DOM, а React отримує доступ через ref:
import { useRef } from 'react'
function UncontrolledForm() {
const emailRef = useRef(null)
const handleSubmit = (e) => {
e.preventDefault()
// Читаємо значення тільки при submit
console.log(emailRef.current.value) // Прямий доступ до DOM
}
// НЕ ПОТРІБЕН useState!
// НЕ ПОТРІБЕН onChange!
// Жодних ре-рендерів при введенні!
return (
<form onSubmit={handleSubmit}>
<input ref={emailRef} defaultValue="" />
<button type="submit">Відправити</button>
</form>
)
}
Життєвий цикл зміни значення:
Переваги uncontrolled:
Недоліки uncontrolled:
| Метрика | Controlled (useState) | Uncontrolled (ref) | React Hook Form |
|---|---|---|---|
| Рендерів при введенні 10 символів | 10 | 0 | 0 |
| Рендерів при зміні 20 полів | 20 | 0 | 0-2* |
| Час монтування форми (20 полів) | ~80ms | ~20ms | ~25ms |
| Споживання пам'яті | Високе | Низьке | Низьке |
*Залежить від використання watch() та інших підписників
React Hook Form — це не просто "wrapper для форм", а складна система з декількома ключовими компонентами.
React Hook Form зберігає посилання на всі інпути в Map-структурі:
// Спрощена внутрішня структура
const fieldsRef = new Map([
['email', { ref: inputElement, options: { required: true } }],
['password', { ref: inputElement, options: { minLength: 8 } }],
])
Коли ви викликаєте register('email'), бібліотека:
Одна з найрозумніших частин — lazy evaluation formState через Proxy:
// Спрощена імплементація
const formState = new Proxy(
{},
{
get(target, prop) {
// Підписуємося тільки на використані властивості!
subscribe(prop)
return calculateFormState(prop)
},
},
)
// Використання
function MyForm() {
const { formState } = useForm()
// Тільки 'isDirty' викликає підписку!
console.log(formState.isDirty)
// 'isValid' НЕ обчислюється, якщо не використовується
// formState.isValid
}
formState в компоненті, жодних обчислень не відбувається. Це величезна оптимізація!Замість додавання валідаторів до кожного інпуту, React Hook Form використовує централізовану систему:
// onChange будь-якого інпуту веде до:
function handleChange(event) {
const fieldName = event.target.name
const fieldConfig = fieldsRef.get(fieldName)
if (shouldValidate(fieldConfig)) {
validateField(fieldName, event.target.value)
}
// Оновлюємо тільки підписників на це поле
notifySubscribers(fieldName)
}
npm install react-hook-form
TypeScript типи включені за замовчуванням — додаткове встановлення не потрібне.
Для початку, давайте подивимося на різницю між традиційним підходом та React Hook Form:
import { useState } from 'react'
function LoginForm() {
const [email, setEmail] = useState('')
const [password, setPassword] = useState('')
const [errors, setErrors] = useState({})
const validate = () => {
const newErrors = {}
if (!email) newErrors.email = "Email обов'язковий"
if (!password) newErrors.password = "Пароль обов'язковий"
if (password.length < 8) newErrors.password = 'Мінімум 8 символів'
setErrors(newErrors)
return Object.keys(newErrors).length === 0
}
const handleSubmit = (e) => {
e.preventDefault()
if (validate()) {
console.log({ email, password })
}
}
return (
<form onSubmit={handleSubmit}>
<input value={email} onChange={(e) => setEmail(e.target.value)} />
{errors.email && <span>{errors.email}</span>}
<input type="password" value={password} onChange={(e) => setPassword(e.target.value)} />
{errors.password && <span>{errors.password}</span>}
<button type="submit">Увійти</button>
</form>
)
}
import { useForm } from 'react-hook-form'
function LoginForm() {
const {
register,
handleSubmit,
formState: { errors },
} = useForm()
const onSubmit = (data) => console.log(data)
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input {...register('email', { required: "Email обов'язковий" })} />
{errors.email && <span>{errors.email.message}</span>}
<input
type="password"
{...register('password', {
required: "Пароль обов'язковий",
minLength: { value: 8, message: 'Мінімум 8 символів' },
})}
/>
{errors.password && <span>{errors.password.message}</span>}
<button type="submit">Увійти</button>
</form>
)
}
Ключові відмінності:
useState для кожного поляonChange handlerssetErrorsregisterimport { useForm } from 'react-hook-form'
function SimpleForm() {
// 1. Ініціалізуємо форму
const {
register, // Функція для реєстрації полів
handleSubmit, // Wrapper для submit handler
formState: { errors }, // Об'єкт з помилками валідації
} = useForm()
// 2. Callback, що викликається при успішному submit
const onSubmit = (data) => {
console.log(data) // { email: '...', password: '...' }
}
return (
<form onSubmit={handleSubmit(onSubmit)}>
{/* 3. Реєструємо поле 'email' з правилами */}
<input
{...register('email', {
required: "Email обов'язковий",
pattern: {
value: /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i,
message: 'Невалідний email',
},
})}
/>
{/* 4. Показуємо помилку, якщо є */}
{errors.email && <span>{errors.email.message}</span>}
<input
type="password"
{...register('password', {
required: "Пароль обов'язковий",
minLength: { value: 8, message: 'Мінімум 8 символів' },
})}
/>
{errors.password && <span>{errors.password.message}</span>}
<button type="submit">Увійти</button>
</form>
)
}
Що відбувається під капотом:
Ініціалізується Map для refs та правил валідації:
{
refs: new Map(),
formState: new Proxy(...),
validators: new Map(),
}
{
name: 'email',
ref: [Function], // Callback для отримання DOM елемента
onChange: [Function], // Handler для валідації
onBlur: [Function], // Handler для onBlur валідації
}
{...register('email')} розгортається у:
<input name="email" ref={...} onChange={...} onBlur={...} />
handleSubmit зчитує всі значення через refs, валідує, і якщо валідно — виклик
ає onSubmit(data)
React Hook Form: Професійна Робота з Формами
Axios та React: Професійна Архітектура Запитів
Уявіть, що ви будуєте панель керування для великого інтернет-магазину. Вам потрібно завантажувати товари, оновлювати ціни в реальному часі, обробляти помилки мережі та скасовувати зайві запити, коли користувач швидко перемикається між вкладками.