useRef: мутабельна пам’ять без рендерингу та прямий доступ до DOM-вузлів.Інтерактивність сучасних веб-додатків повністю побудована на реагуванні на дії користувача — події. У світі Vanilla JavaScript ми звикли до addEventListener, проте React абстрагує цей процес, створюючи власну паралельну систему подій для забезпечення кросбраузерності та надзвичайно високої продуктивності. Глибоке розуміння цієї системи відрізняє Senior-розробника від початківця. Крім того, робота з формами є одним із найскладніших аспектів React-розробки через постійні компроміси між зручністю розробки (DX) та продуктивністю клієнта.
Якщо у React-компоненті ви пишете <button onClick={handleClick}>, ви не вішаєте нативний обробник подій браузера безпосередньо на DOM-вузол кнопки.
Що таке SyntheticEvent?
React перехоплює нативну подію браузера (наприклад, MouseEvent) і обгортає її у власний клас — SyntheticEvent.
e.target || e.srcElement), React нормалізує це “під капотом”.Capture, наприклад, onClickCapture).В 16-й версії React існував механізм Event Pooling (перевикористання об’єктів подій для економії пам’яті), який змушував використовувати e.persist(), щоб мати доступ до події всередині setTimeout. У React 17 від цього пулінгу відмовилися заради спрощення асинхронного коду, тому сучасні синтетичні події поводяться так само, як і нативні.
Уявіть таблицю на 10 000 користувачів, де кожен рядок має кнопку “Видалити”. Якщо повісити нативний addEventListener на кожну кнопку, браузер використає величезну кількість пам’яті (Memory Leak potential), і сторінка почне “гальмувати”.
Як працює React:
React використовує Event Delegation. Замість того, щоб вішати 10 000 обробників, React вішає рівно ОДИН обробник (на подію click) на найвищому рівні. Коли користувач клікає, подія спливає (Bubbling) до цього єдиного обробника, і React самостійно вирішує, який саме компонент має на неї зреагувати на основі власного віртуального дерева (Fiber Tree).
Зміна в React 17:
До React 17 цей єдиний глобальний обробник вішався на document. Це викликало конфлікти, якщо на одній сторінці було кілька додатків React або мікс із jQuery. Від React 17 події делегуються на корінь (Root Node) — тобто на той div (зазвичай <div id="root">), у який рендериться ваш конкретний додаток-мікрофронтенд.
Робота з формами в React ділиться на два кардинально різних підходи. Перший і рекомендований — Керовані компоненти.
Суть: У класичному HTML елементи <input>, <textarea>, <select> самі керують своїм локальним станом на рівні браузера (натиснули клавішу -> браузер зберіг літеру і показав в інпуті). У React ми забираємо цю владу в браузера і передаємо в useState. State стає Єдиним Джерелом Істини (Single Source of Truth).
const ControlledForm = () => {
const [email, setEmail] = useState("");
// Обробник стає "шлюзом" - ми можемо валідувати, форматувати (напр. toLowerCase) на льоту
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setEmail(e.target.value.toLowerCase());
};
return (
<input
type="email"
value={email} // Інпут показує ТІЛЬКИ те, що дозволяє React
onChange={handleChange}
/>
);
};
Переваги: Миттєва валідація, умовне блокування кнопки сабміту (Submit), маскування введення (Input Masking - номери телефонів, кредитні картки).
useRefІноді повний контроль через useState шкодить. Кожне натискання клавіші в Controlled Component викликає перерендер усієї форми. Якщо форма складається з 50 важких полів, введення тексту почне гальмувати.
Для таких сценаріїв використовують Некеровані компоненти. Їх суть: ми дозволяємо браузеру самостійно керувати станом інпутів, а дані забираємо “на вимогу” (лише в момент натискання кнопки Submit) за допомогою посилань (Refs).
const UncontrolledForm = () => {
// Створюємо посилання, прив'язуємо до DOM, але це НЕ ВИКЛИКАЄ рендерів
const fileInputRef = useRef<HTMLInputElement>(null);
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
// Отримуємо дані "на вимогу", безпосередньо з DOM
console.log(fileInputRef.current?.files?.[0]?.name);
};
return (
<form onSubmit={handleSubmit}>
{/* Файл-інпути завжди некеровані в React (через питання безпеки) */}
<input type="file" ref={fileInputRef} />
<button type="submit">Upload</button>
</form>
);
};
Чому файлові інпути в React завжди некеровані? В React керований компонент означає, що стан (state) є “єдиним джерелом істини” для значення інпуту. Однак для це неможливо через безпеку браузера.
Приклад загрози: Якби скрипт міг керувати значенням, зловмисник міг би додати на сторінку прихований інпут:
<input type="file" value="C:/Users/Admin/secrets/passwords.txt" />
Після чого скрипт автоматично відправив би форму (form.submit()), викравши ваш файл без вашого відома.
JavaScript: Може лише прочитати вибраний файл, але не може змінити шлях до нього або вибрати файл за користувача.
useRef: мутабельна пам’ять без рендерингуХук useRef є архітектурним антиподом (протилежністю) хуку useState.
Вимоги до useState: незмінність (Immutability), зміна setState викликає новий рендер.
Вимоги до useRef: мутабельність, зміна ref.current НЕ викликає рендер.
У useRef є два абсолютно різних сценарії використання (Use Cases):
1. Доступ до DOM-елементів (Imperative Handle):
Для фокусування інпутів (inputRef.current.focus()), інтеграції зі сторонніми бібліотеками, які потребують нативного DOM-вузла (наприклад D3.js для графіків, або Google Maps API).
2. Мутабельний “Сейф” пам’яті (Instance Variables): Як зберігати дані (наприклад ID таймера або попереднє значення пропсів), щоб вони пережили рендер, але зміна цих даних не “смикала” інтерфейс?
const TimerComponent = () => {
const [ticks, setTicks] = useState(0);
const timerId = useRef<number | null>(null); // Не спричиняє рендеру
const start = () => {
// Ми МУТУЄМО об'єкт напрямую
timerId.current = window.setInterval(() => setTicks((t) => t + 1), 1000);
};
const stop = () => {
if (timerId.current) clearInterval(timerId.current);
};
// ...
};
Правило безпеки: Ніколи не читайте і не записуйте в ref.current безпосередньо в тілі функції-рендера. Це робиться ТІЛЬКИ в обробниках подій (onClick) або всередині useEffect. Інакше ви порушите чистоту компонента.
Побудова великих корпоративних форм (з валідацією, помилками, брудним станом isDirty, isTouched) на чистих useState перетворюється на непідтримуваний спагеті-код (Spaghetti Code).
Для вирішення цієї інженерної проблеми індустрія використовує бібліотеки управління формами. Світовий стандарт сьогодні — React Hook Form.
Чому не useState?
React Hook Form працює на базі некерованих компонентів (useRef), але пропонує синтаксис, схожий на керовані. Перевага феноменальна: при введенні тексту оновлюється лише внутрішній стан бібліотеки (без виклику React Re-render). Рендер екрану відбувається лише тоді, коли з’являється помилка валідації. Це зменшує кількість непотрібних обчислень на порядки.
import { useForm } from "react-hook-form";
const ComplexForm = () => {
// register "прив'язує" інпут до внутрішнього useRef-накопичувача
const {
register,
handleSubmit,
formState: { errors },
} = useForm();
const onSubmit = (data) => console.log(data); // Обробка "на вимогу"
return (
<form onSubmit={handleSubmit(onSubmit)}>
{/* Валідація не викликає ререндер на КОЖЕН символ */}
<input
{...register("email", { required: true, pattern: /^\S+@\S+$/i })}
/>
{errors.email && <span>Помилка валідації!</span>}
<button type="submit">Send</button>
</form>
);
};
e.preventDefault()Дуже часто події трапляються надто часто (наприклад, onChange під час швидкого введення пошукового запиту у поле з автокомплітом, який посилає HTTP-запити на сервер).
useDebounce), щоб відкласти виконання функції або оновлення стану.<form>. За замовчуванням браузер виконає жорстке HTTP-перезавантаження (Full Page Refresh) сторінки. Завжди викликайте e.preventDefault(), щоб зламати нативну поведінку і дозволити React обробити дані через API-запит.onClick на перехід у профіль).useState), оскільки він дає максимальний контроль для миттєвої валідації та залежних полів.input type="file".useRef є своєрідним “чорним ходом” (Escape Hatch) у декларативній системі React. Він дозволяє будувати мутабельні референси, робота з якими “невидима” для процесів Reconciliation та Rendering. В ідеальному декларативному React-додатку пряме (імперативне) використання useRef для маніпуляцій DOM повинне бути зведено до мінімуму.value).addEventListener) безпосередньо на кожен вкладений DOM-елемент, і що таке Event Delegation?document на root div) у React 17 вплинула на архітектуру мікрофронтендів (коли кілька додатків працюють на одній сторінці)?useRef називають “сейфом (Escape Hatch) поза системою рендерингу” в парадигмі React? У чому функціональна різниця між ініціалізацією таймера через useRef та через useState?ref.current у тілі обчислення функції-компонента”, і дозволяє це робити лише у обробниках подій (onClick) та хуку життєвого циклу (useEffect)?