Розбираємося з хуками в React

💡 Усі статті, обговорення, новини про Front-end — в одному місці. Приєднуйтесь до Front-end спільноти!

Привіт! Я — Настя, Front-end розробниця у WiX, яка любить занурюватися в те, як різні інструменти працюють під капотом.

У цій статті ми будемо разом розбиратися з хуками та спробуємо їх максимально демістифікувати. Моя ціль у тому, щоб кожен з читачів міг впевнено користуватися ними і мав розуміння їхніх правил. Ми поговоримо про те, що хуки таке і навіщо вони нам потрібні, про їхні особливості, про сфери застосування, а також про створення своїх кастомних хуків.

Що таке хуки

У React хуки — це спеціальні функції, які дозволяють нам «підключитися» (англ. hook into) до внутрішніх механізмів бібліотеки. Іншими словами, хуки — це API для внутрішнього функціоналу React. Для прикладу, стан зберігається у Fіber-дереві, до якого в нас немає прямого доступу зовні. Тому для виконання маніпуляцій зі станом нам потрібні абстракції у вигляді хуків.

На момент написання цієї статті в React є 17 вбудованих хуків (два з яких ще в canary). Крім того, інші бібліотеки, такі як React Router або Redux, також впроваджують свої власні хуки.

За допомогою хуків ми можемо:

  1. створити стан та отримати доступ до нього;
  2. зареєструвати побічні ефекти;
  3. отримати та зберегти DOM-вузли;
  4. отримати доступ до контексту;
  5. покращити перформанс застосунку.

Цей перелік, звісно ж, не є вичерпним. Тут наведено лише найпоширеніші можливості, які надають хуки.

Особливості хуків

Як ви могли вже помітити, всі хуки починаються зі слова 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
До обраногоВ обраному9
LinkedIn
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter

Як для першої статті норм, але далі так не піде)

Як ви могли вже помітити, всі хуки починаються зі слова use. Так React відрізняє їх від звичайних функцій. Якщо ви пишете кастомні хуки (про які ми поговоримо детальніше згодом), треба обов’язково користуватися цим правилом, коли обираєте їм назву.

а що ж буде якщо не почати назву функції з use, реакт не дізнається що це хук і нічого не буде працювати?

Я думав ми під капот хукам дивитись будемо, а тут інфа, яку сьогодні кожен джун має як «отче наш» знати(

Дякую за цікаву статтю.
Звичайно, код у прикладах схематичний, але тим не менше, у прикладі про фетч змінна endpoint мала би бути apiEndpoint або навпаки.

Писати реальний код в статті це ознака хорошего тону)

Мені сподобалося, дякую авторці за статтю. Єдине, хотілось би почути про useRef, для мене він самий ’містичний’).

Хвала новій доці по реакту, тут аж 6 прикладів по цьому хуку)
react.dev/...​erence/react/useRef#usage

Після виведення «містики» в console.log вона потроху пропадає)

Стаття і правда цікава, наприклад, хінт про fiber-дерево, про що не замислюєшся майже ніколи і бачив один раз десь в документації сто років тому.

Почалось все дуже гарно і з деталями, але хочется більше тексту — додайте інфо про інші хуки, все ж таки назва «розбираємось з хуками», а не «коротко про useEffect та useState».

Пишете гарно, з доповненнями можна було би використовувати як cheat-sheet перед співбесідою.

Як автор схожої статті dou.ua/...​s/react-hooks-guide/скажу вам, що однієї публікації буде не достатньо для такої великої теми

Дякую за цікаву статтю!
Мені здається варто було знайти якийсь інший приклад використання useEffect, адже дата фетч за його допомогою це вже трохи bad practice

Знаю таку думку, але не підтримую)
Юз ефект з фетч використовується в дуже багатьох нових і старих кодбазах

Все от размера проекта зависит, для небольших прототипов ставить-настраиваить Redux или TanstackQuery это перебор.

Виявляється, що хуки можна використовувати в if, наприклад новий хук «use» можна використовувати замість useContext. Також use можна в if обгортати. Якими ще трюками Реакт нас здивує?! Не думав що прийде час коли React змусе мене в сторону Angular дивитися, або WebComponents. На жаль в офіційній документації React дуже мало описано принципів реалізації.

Так, use має свої особливості, але ця стаття була написана до 19-го реакту)

Я зрозумів) Стаття супер. До речі я таки знайшов статтю як use працює під капотом і що це за звір. Виявляється це аналог await для клієнтський компонентів. Для серверних компонентів використовуємо async/await, а для клієнтський use, бо вони повинні бути синхронними. use розгортає значення проміса, при цьому наш компонент синхронний. Ось стаття де розказується хак під назвою use)
Кого мучив цей хук, може глянути як він працює під капотом.
github.com/...​s-support-for-promises.md

Підписатись на коментарі