Написання хуків React: useState, useEffect, useReducer та користувацькі хуки
Дізнайтеся, як правильно використовувати useState, useEffect, useReducer та користувацькі хуків у TypeScript, а також коли вибір TypeScript замість звичайного JavaScript справді приносить користь.
Помилки типів, які виявляються під час компіляції, захищають вас від сеансів дебаггінгу, які інакше могли б тривати до пізної ночі, і саме тому команди, які поєднують React з TypeScript, вважають типовані хуки основою, а не розкішшю. Опанування способів типізації стану, ефектів, редукторів та користувацьких хуків — а також розуміння того, коли взагалі доцільно використовувати TypeScript — є двома аспектами однієї практичної навички для створення надійних React-додатків.
Чому безпека типів має бути частиною ваших хуків
React Hooks вже надали розробникам більш чистий та модульний спосіб структурування компонентів. TypeScript додає ще один рівень безпеки до цієї структури, щоб помилки проявлялися під час написання коду, а не після того, як користувач натрапить на несправний інтерфейс у продакшені.
З самого початку хуки були розроблені так, щоб логіка, яку можна повторно використовувати, була передбачуваною — на це вказував Ден Абрамов під час обговорення філософії проектування React. Внесок TypeScript полягає у тому, що він робить цю передбачуваність явною та підлягаючою перевірці, замість того щоб залишати її лише у вашій пам’яті.
Правильне визначення типу useState
У більшості повсякденних випадків TypeScript самостійно визначає тип вашого стану без жодної допомоги:
// inferred as boolean, no annotation needed
const [isLoading, setIsLoading] = useState(false);
Ситуація змінюється, коли початкове значення дорівнює null або undefined — у такому разі вам потрібно самостійно позначити тип, замість того щоб покладатися на автоматичне визначення:
interface UserProfile {
id: string;
name: string;
avatarUrl: string;
}
const [user, setUser] = useState<UserProfile | null>(null);
Корисною звичкою тут є стримування бажання вдаватися до універсального типу лише для того, щоб приховати повідомлення про помилку. Це призводить до втрати всіх переваг, які ви спочатку намагалися отримати завдяки TypeScript.
Типування useEffect: простіше, ніж очікувалося
Генерики насправді не застосовуються до useEffect, але все одно потрібно бути обережним із масивами залежностей та логікою очищення:
useEffect(() => {
const controller = new AbortController();
const fetchUser = async () => {
const res = await fetch(`/api/users/${userId}`, {
signal: controller.signal,
});
const data: UserProfile = await res.json();
setUser(data);
};
fetchUser();
return () => controller.abort(); // cleanup on unmount
}, [userId]);
Типування useReducer для складнішого стану
Саме тут переваги TypeScript стають очевидними:
type CartAction =
| { type: 'ADD_ITEM'; payload: CartItem }
| { type: 'REMOVE_ITEM'; payload: string }
| { type: 'CLEAR_CART' };
function cartReducer(state: CartItem[], action: CartAction): CartItem[] {
switch (action.type) {
case 'ADD_ITEM':
return [...state, action.payload];
case 'REMOVE_ITEM':
return state.filter((item) => item.id !== action.payload);
case 'CLEAR_CART':
return [];
default:
return state;
}
}
Завдяки такій моделі типів диспетчування недійсних дій призводить до помилки на етапі компіляції, яку можна виявити негайно, замість того щоб зіткнутися з несподіванкою під час виконання програми.
Створення повторно використовуваних кастомних хуків за допомогою генериків
function useLocalStorage<T>(key: string, initialValue: T) {
const [value, setValue] = useState<T>(() => {
const stored = window.localStorage.getItem(key);
return stored ? JSON.parse(stored) : initialValue;
});
useEffect(() => {
window.localStorage.setItem(key, JSON.stringify(value));
}, [key, value]);
return [value, setValue] as const;
}
Оскільки цей хук є генеричним, його можна використовувати будь-де у вашому кодбазі — з рядками, об’єктами чи масивами — зберігаючи повну точність типів для будь-яких даних, які ви передаєте через нього.
Коротко
- Дозвольте TypeScript самостійно визначати простий стан, але будьте чіткими, коли значення може бути null або undefined.
- Моделюйте дії useReducer як дискриміновану унію.
- Використовуйте генерики у власних хуках, щоб зробити їх придатними для повторного використання з різними типами даних.
- Розглядайте
anyяк скорочення, яке потайко коштує вам більше, ніж економить згодом.
Вибір між звичайним JavaScript та TypeScript
Як тільки ви зрозумієте, наскільки безпеку надають типовані хуки, природно виникає ширше питання: чи справді кожен проект має використовувати TypeScript, чи це іноді є надмірною оптимізацією? Протягом довгого часу це розглядалося як бінарний вибір, і обрання будь-якого з варіантів означало прийняття реальних компромісів.
Раніше використання TypeScript означало необхідність працювати з конвеєрами компіляції, інструментами на кшталт ts-node та великою кількістю файлів конфігурації лише для того, щоб запустити скрипт. Тепер, коли такі середовища виконання, як Node.js, підтримують видалення вбудованих типів, ці труднощі значно зменшилися, і межа між написанням звичайного JavaScript та TypeScript стала набагато тоншою, ніж раніше.
Якщо розглянути, як кожен варіант впливає на ваш щоденний робочий процес, стане легше вирішити, який з них справді підходить для проекту, що перед вами.
Чому звичайний JavaScript все ще має своє місце
JavaScript — це мова, яку браузер та Node.js розуміють нативно. Ви пишете файл, запускаєте його, і він негайно виконується, без необхідності використання компілятора та без додаткових оголошень.
- Миттєве створення прототипів: коли ви тестуєте ідею, швидко складаєте скрипт або створюєте невеликий MVP, JavaScript дозволяє вам діяти так швидко, наскільки це можливо.
- Немає необхідності в налаштуваннях: немає потреби у файлі
tsconfig.jsonчи кроці перевірки типів лише для підтвердження того, що функція виконує потрібні дії. - Менше розумового навантаження: ваша увага залишається зосередженою на справжній логіці програми, а не на оголошеннях типів чи проблемах компілятора.
Недоліки проявляються, коли кодова база на JavaScript перевищує кілька тисяч рядків. У такому випадку рефакторинг стає ризикованим — перейменування властивості у двадцяти файлах означає необхідність використання функцій пошуку та заміни на глобальному рівні та сподівання, що ніщо не зламається під час запуску додатку.
Чому TypeScript виправдовує себе з ростом проектів
TypeScript додає до JavaScript статичну систему типів, яка функціонує як автоматичний перевірювач, що вказує на проблеми під час написання коду — наприклад, попереджаючи вас у момент спроби передати рядок до функції, яка очікує число.
- Документація, яка залишається точною: типи виконують роль динамічної документації. Інтерфейси повідомляють про точну структуру об’єкта, не змушуючи вас переглядати кілька файлів з реалізацією.
- Безпечніша рефакторизація: якщо ви змінюєте модель даних або схему бази даних, компілятор вказує на кожне місце в кодовій базі, яке тепер потребує оновлення.
- Краща підтримка редактором: інструменти на кшталт VS Code чи Cursor використовують сервер мови TypeScript для швидкого автодоповнення та підсвічування помилок прямо під час роботи.
Історично цінуванням був справжній „податок на інструменти“ — налаштування компіляторів, карт джерела та скриптів-обгорток додавало додаткові кроки до того, що раніше було простим процесом роботи.
Чому це цінування більше не діє так само
Стара скарга про те, що „налаштування TypeScript — це занадто багато клопоту“, вже не має такої сили. Завдяки видаленню типів поточні версії Node.js можуть виконувати файли TypeScript без окремого кроку компіляції.
Насправді движок просто видаляє ваші анотації типів та оголошення інтерфейсів, залишаючи звичайний JavaScript для негайного виконання. Це означає, що ви зберігаєте безпеку статичних типів під час розробки без додаткового навантаження від налаштувань, яке раніше супроводжувало їх.
Підбір інструменту під завдання
Для одноразового скрипту, невеликої автоматизаційної задачі чи проекту, створеного виключно для вивчення принципів роботи Інтернету, звичайний JavaScript залишається кращим вибором. Відсутність додаткової структури робить процес легким та приємним.
Для майже всього іншого — командних кодових баз, додатків з кількома взаємодіючими моделями даних чи будь-яких проектів, які планується підтримувати довше ніж місяць, — невелика кількість налаштувань, які потребує TypeScript, справді варта уваги. Оскільки сучасні середовища виконання ефективно обробляють код у будь-якому випадку, насправді немає потреби обирати між гнучкістю та безпекою: простота JavaScript та механізми контролю TypeScript доступні обидві, і вибір залежить переважно від того, наскільки довготривалим та співпрацею орієнтованим буде проект.
Пов’язана література
- Зміни стандартних налаштувань у TypeScript 6.0: практичний посібник з міграції — Дізнайтеся, які дев’ять стандартних налаштувань компілятора TypeScript 6.0 змінилися, як налаштувати tsconfig для 2026 року та як підготувати кодові бази до версії TypeScript 7, заснованої на Go.
- Стандартні налаштування повної стек-архітектури JavaScript у 2026 році: TypeScript, RSC та інше — Пояснюється, чому TypeScript, React Server Components та більш ефективний підхід до керування станом стали стандартною технологічною стек-архітектурою для команд, що працюють з JavaScript, у 2026 році.