Розбираємося з хуками в React
Привіт! Я — Настя, Front-end розробниця у WiX, яка любить занурюватися в те, як різні інструменти працюють під капотом.
У цій статті ми будемо разом розбиратися з хуками та спробуємо їх максимально демістифікувати. Моя ціль у тому, щоб кожен з читачів міг впевнено користуватися ними і мав розуміння їхніх правил. Ми поговоримо про те, що хуки таке і навіщо вони нам потрібні, про їхні особливості, про сфери застосування, а також про створення своїх кастомних хуків.
Що таке хуки
У React хуки — це спеціальні функції, які дозволяють нам «підключитися» (англ. hook into) до внутрішніх механізмів бібліотеки. Іншими словами, хуки — це API для внутрішнього функціоналу React. Для прикладу, стан зберігається у Fіber-дереві, до якого в нас немає прямого доступу зовні. Тому для виконання маніпуляцій зі станом нам потрібні абстракції у вигляді хуків.
На момент написання цієї статті в React є 17 вбудованих хуків (два з яких ще в canary). Крім того, інші бібліотеки, такі як React Router або Redux, також впроваджують свої власні хуки.
За допомогою хуків ми можемо:
- створити стан та отримати доступ до нього;
- зареєструвати побічні ефекти;
- отримати та зберегти DOM-вузли;
- отримати доступ до контексту;
- покращити перформанс застосунку.
Цей перелік, звісно ж, не є вичерпним. Тут наведено лише найпоширеніші можливості, які надають хуки.
Особливості хуків
Як ви могли вже помітити, всі хуки починаються зі слова use. Так React відрізняє їх від звичайних функцій. Якщо ви пишете кастомні хуки (про які ми поговоримо детальніше згодом), треба обов’язково користуватися цим правилом, коли обираєте їм назву.
Хуки можна викликати тільки в межах функцій. Тобто ми можемо їх використовувати в функціональних компонентах або кастомних хуках. Вони не працюють в класах.
Та найцікавіша особливість хуків — це те, що їх можна використовувати тільки на найвищому рівні компонента. На практиці це означає, що ми не можемо викликати хуки всередині умов (if), циклів, інших функцій або після раннього return. Це правило забезпечує виклик хуків в одному і тому ж порядку при кожному ререндері компонента.
Пропоную зануритися глибше в React для повного розуміння цієї особливості хуків. Хуки, а також пропси, список апдейтів, стейт та інші дані важливі для коректної роботи React, зберігаються у Fiber-дереві.
Структура даних для збереження хуків називається зв’язаним списком, де кожен елемент має вказівник на наступний, вони йдуть ніби ланцюжком. Якщо один з використаних хуків не буде викликано під час ререндеру, це порушить порядок зв’язаного списку та призведе до некоректної роботи. Адже для ідентифікації значення певного хука використовується саме його порядок, в нас немає іншого ідентифікатора, типу назви або айді.
Заміна класовим методам життєвого циклу
Хуки працюють винятково з функціональними компонентами. Але вони дозволяють реалізувати функціонал, який зазвичай використовується в класових компонентах. Особисто мені зручніше використовувати хуки, оскільки вони здаються логічними та простішими в розумінні.
Класовий метод componentDidMount
спрацьовує відразу після вбудови компонента в DOM-дерево. На практиці тут я зазвичай виконую початкові налаштування, такі як отримання даних з API або налаштування обробників подій.
Якщо ми хочемо досягти такого ж ефекту з використанням хуків, ми можемо скористатися useEffect
з порожнім масивом залежностей:
useEffect(() => { fetchUsers(); }, []);
Але справжню силу хуків можна відчути в порівнянні з методом componentDidUpdate
. Цей метод у класових компонентах викликається на кожен ререндер. З хуками ми можемо досягти цього ж ефекту, передавши в масив залежностей useEfect
змінні, які ми «відстежуємо», тобто при зміні яких повинна статися певна дія (викликатися колбек).
Водночас ми можемо створювати стільки окремих незалежних юз ефектів, скільки нам треба, тим самим сепаруючи логіку.
useEffect(() => { fetchUser(userId); }, [userId]);
Метод життєвого циклу componentWillUnmount
спрацьовує безпосередньо перед тим, як компонент буде видалений з DOM. Я користуюсь ним для очищення таймерів, підписок або локальних даних. У функціональних компонентах це можна реалізувати за допомогою cleanup функції в useEffect
. Вона спрацьовує перед кожним викликом хука (крім першого), а також перед видаленням компоненту з DOM. Якщо масив залежности пустий — ефект такий самий, як в
componentWillUnmount
.
useEffect(() => { return () => { clearTimeout(timeoutId); }; }, []);
Кастомні хуки
React надає нам можливість перевикористовувати логіку та створювати свої кастомні хуки. Зазвичай для цього ми робимо просту функцію, наприклад, в utils.
Але якщо задіяна логіка, яка в собі містить хуки, звичайна функція нам не допоможе. Адже, як ми дізналися раніше, хуки можна викликати тільки на найвищому рівні. Для цього в React і були впроваджені кастомні хуки. По суті, це такі ж функції, але в них нам дозволяється викликати інші хуки.
Наприклад розглянемо створення кастомного хука для отримання даних з API. Уявімо, що в нас є кілька компонентів, які повинні витягти дані з різних ендпоінтів. Ми можемо створити простий хук, який буде мати в собі стан для даних, статус завантаження та обробку помилок:
function useApiData(endpoint) { // State to store the fetched data const [data, setData] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); useEffect(() => { const fetchData = async () => { try { const response = await fetch(apiEndpoint); if (!response.ok) { throw new Error(`Error: ${response.status}`); } const result = await response.json(); setData(result); } catch (error) { setError(error.message); } finally { setLoading(false); } }; fetchData(); }, [apiEndpoint]); // Return the state and loading/error information for external use return { data, loading, error }; }
Цим простим хуком ми зробили абстракцію за логікою fetch, і тепер нам не треба піклуватися про імплементацію. Ми просто маємо результат, а саме: дані, поточний стан загрузки та помилки.
За часів класових компонентів перевикористання логіки було набагато складнішим. Розробники зазвичай користувалися патернами, такими як render props або HOC (higher-order component). Вони не були такими чистими і зрозумілими, як кастомні хуки, а також змінювали ієрархію компонентів, призводячи до так званого «пекла обгорток» (wrapper hell). Це і було однією з основних мотивацій для впровадження хуків.
Обов’язковим правилом створення кастомних хуків є те, що вони завжди повинні починатися зі слова use. Також важливо, щоб в кожного хука була своя конкретна та зрозуміла мета і описова назва. Він може приймати будь-які аргументи, а також вертати будь-які дані, які потрібні для імплементації. Всередині кастомного хука ми зазвичай використовуємо один або декілька інших хуків.
Приклади хуків, які часто доводиться писати на практиці:
useDebounce
;useLocalStorage
;useThrottle
;useGeolocation
;
Цей патерн перевикористання логіки користується такою популярністю, що на просторах npm
є ліби з великим вибором кастомних хуків (наприклад, ліба useHooks
).
Висновки
У цій статті ми детально розглянули концепцію хуків в React та їхні особливості. Сподіваюсь, мені вдалося довести, що хуки є потужним інструментом, бо вони дозволяють зручно працювати зі станом, побічними ефектами та іншими аспектами компонентів.
Ми розглянули вбудовані хуки, які надає React, такі як useState
, useEffect
та інші, а також розглянули можливості їхнього використання для створення функціональних компонентів, які замінюють класові методи життєвого циклу.
Особливу увагу було приділено кастомним хукам, що дозволяють перевикористовувати логіку в різних частинах застосунку. Ми розглянули приклад створення кастомного хука для отримання даних з API, показуючи, як цей підхід спрощує код та полегшує його читання.
Розуміння хуків та їхніх принципів дозволяє створювати декларативний та легко читабельний код, покращуючи продуктивність та обслуговуваність проєктів.
19 коментарів
Додати коментар Підписатись на коментаріВідписатись від коментарів