Огляд хуків

Хуки — новинка у React 16.8, яка дозволяє використовувати стан та інші можливості React без написання класу.

Хуки зворотньо сумісні. Майте на увазі, що це біглий огляд хуків, який більше підійде для досвідчених користувачів React. Якщо ви заплутаєтесь, шукайте такий жовтий блок:

Докладне пояснення

Якщо ви хочете зрозуміти, навіщо ми додаємо хуки в React, ознайомтесь із розділом Мотивація.

↑↑↑ Кожен розділ закінчується таким жовтим блоком. Кожен блок містить посилання на докладне пояснення.

📌 Хук стану

Розглянемо приклад, в якому рендериться лічильник. Коли ви натискаєте кнопку, значення лічильника збільшується:

import React, { useState } from 'react';
function Example() {
  // Оголошуємо нову змінну стану "count"  const [count, setCount] = useState(0);
  return (
    <div>
      <p>Ви натиснули {count} разів</p>
      <button onClick={() => setCount(count + 1)}>
        Натисни мене
      </button>
    </div>
  );
}

У цьому прикладі, useState — це хук (визначення хуку наведенно нижче). Ми викликаємо його для того, щоб надати внутрішній стан нашому компоненту. React буде зберігати цей стан між повторними рендерами. Виклик useState повертає дві речі: поточне значення стану та функцію, яка дозволяє оновлювати цей стан. Ви можете викликати цю функцію де завгодно, наприклад, в обробнику події. Вона подібна до this.setState у класах, за винятком того, що не об’єднує новий та старий стан. Порівняння хука useState та this.state приведено на сторінці Використання хука стану.

Єдиним аргументом для useState є початкове значення стану. У наведеному вище прикладі — це 0, тому що наш лічильник починається з нуля. Зауважте, що на відміну від this.state, у нашому випадку стан може, але не зобов’язаний, бути об’єктом. Початкове значення аргументу використовується тільки під час першого рендера.

Оголошення декількох змінних стану

Ви можете використовувати хук стану більше одного разу в одному компоненті:

function ExampleWithManyStates() {
  // Оголошуємо декілька змінних стану!
  const [age, setAge] = useState(42);
  const [fruit, setFruit] = useState('банан');
  const [todos, setTodos] = useState([{ text: 'Вивчити хуки' }]);
  // ...
}

Синтаксис деструктуризації масивів дозволяє нам по різному називати змінні стану, які ми оголошуємо при виклику useState. Ці імена не є частиною API useState. Натомість, React припускає, що якщо ви викликаєте useState багато разів, то ви робите це в тому ж порядку під час кожного рендеру. Ми пояснимо, чому це працює та коли це стане в нагоді, трохи пізніше.

Що ж таке хук?

Хуки — це функції, за допомогою яких ви можете “зачепитися” за стан та методи життєвого циклу React з функційних компонентів. Хуки не працюють всередині класів — вони дають вам можливість використовувати React без класів. (Ми не рекомендуємо відразу ж переписувати наявні компоненти, але за бажанням, ви можете почати використовувати хуки у своїх нових компонентах.)

React містить кілька вбудованих хуків, таких як useState. Ви також можете створювати власні хуки, щоб повторно використовувати їх в інших своїх компонентах. Для початку, розглянемо вбудовані хуки.

Докладне пояснення

Ви можете дізнатися більше на сторінці: Використання хука стану.

⚡️ Хук ефекту

Вам, напевно, доводилося створювати запити даних, робити підписки або вручну змінювати DOM з React-компонента. Ми називаємо ці операції “побічними ефектами” (або скорочено “ефекти”), так як вони можуть впливати на роботу інших компонентів і не можуть бути виконані під час рендеринга.

За допомогою хука ефекту useEffect ви можете виконувати побічні ефекти із функційного компонента. Він виконує таку ж саму роль, що і componentDidMount, componentDidUpdate та componentWillUnmount у React-класах, об’єднавши їх в єдиний API. (Ми порівняємо useEffect з іншими методами на сторінці Використання хука ефекту.)

Наприклад, цей компонент встановлює заголовок документа після того, як React оновлює DOM:

import React, { useState, useEffect } from 'react';
function Example() {
  const [count, setCount] = useState(0);

  // Подібно до componentDidMount та componentDidUpdate:  useEffect(() => {    // Оновлюємо заголовок документа, використовуючи API браузера    document.title = `Ви натиснули ${count} разів`;  });
  return (
    <div>
      <p>Ви натиснули {count} разів</p>
      <button onClick={() => setCount(count + 1)}>
        Натисни мене
      </button>
    </div>
  );
}

Коли ви викликаєте useEffect, React отримує вказівку запустити вашу функцію з “ефектом” після того, як він відправив зміни у DOM. Оскільки ефекти оголошуються всередині компонентів, то у них є доступ до пропсів та стану. За замовчуванням, React запускає ефекти після кожного рендеру, включаючи перший рендер. (Ми розглянемо більш докладно, як це відрізняється від класових методів життєвого циклу на сторінці Використання ефекту хука.)

Ефект також може повертати функцію, яка вказуватиме, як за ним слід здіснити “прибрання”. Наприклад, цей компонент використовує ефект, щоб підписатися на статус друга в мережі, і виконує прибирання, відписуючись від нього:

import React, { useState, useEffect } from 'react';

function FriendStatus(props) {
  const [isOnline, setIsOnline] = useState(null);

  function handleStatusChange(status) {
    setIsOnline(status.isOnline);
  }

  useEffect(() => {    ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);    return () => {      ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);    };  });
  if (isOnline === null) {
    return 'Завантаження...';
  }
  return isOnline ? 'В мережі' : 'Не в мережі';
}

У наступному прикладі, React буде відписуватись від нашого ChatAPI перед демонтажем компонента та перед перезавантаженням ефекту у повторному рендері. (Ви можете зробити так, щоб React не підписувався заново до API, якщо props.friend.id, який ми передали до ChatAPI, залишається без змін.)

Подібно до useState, ви можете використовувати більше одного ефекту в документі:

function FriendStatusWithCounter(props) {
  const [count, setCount] = useState(0);
  useEffect(() => {    document.title = `Ви натиснули ${count} разів`;
  });

  const [isOnline, setIsOnline] = useState(null);
  useEffect(() => {    ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
    return () => {
      ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
    };
  });

  function handleStatusChange(status) {
    setIsOnline(status.isOnline);
  }
  // ...

Хуки дають вам можливість організовувати побічні ефекти як цілісні шматки функціональності (наприклад, додавання і скасування підписки), замість того, щоб примушувати вас ділити все згідно з методами життєвого циклу.

Докладне пояснення

Ви можете дізнатися більше про useEffect на сторінці Використання хука ефекту.

✌️ Правила хуків

Хуки — це функції JavaScript, але вони накладають два додаткових правила:

  • Хуки слід викликати тільки на найвищому рівні. Не викликайте хуки всередині циклів, умов або вкладених функцій.
  • Хуки слід викликати тільки з функційних React-компонентів. Не викликайте хуки зі звичайних функцій JavaScript. (Є тільки один виняток, звідки ще можна викликати хуки — це з ваших власних хуків. Ми розповімо про них далі.)

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

Докладне пояснення

Ви можете дізнатися більше на сторінці правила хуків.

💡 Створення власних хуків

Інколи буває потрібно повторно використати одну й ту ж логіку стану в декількох компонентах. Традиційно для цього використовувалися два підходи: компоненти вищого порядку та рендер-пропси. За допомогою користувацьких хуків це завдання вирішується без додавання непотрібних компонентів у ваше дерево.

Раніше на цій сторінці ми розглядали компонент FriendStatus, який викликав хуки useState та useEffect для того, щоб підписатися на статус друга в мережі. Припустимо, що ми хочемо використати цю логіку з підпискою ще раз, але вже в іншому компоненті.

Перш за все, винесімо цю логіку в користувацький хук useFriendStatus:

import React, { useState, useEffect } from 'react';

function useFriendStatus(friendID) {  const [isOnline, setIsOnline] = useState(null);

  function handleStatusChange(status) {
    setIsOnline(status.isOnline);
  }

  useEffect(() => {
    ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange);
    return () => {
      ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange);
    };
  });

  return isOnline;
}

Хук приймає friendID як аргумент і повертає значення, яке показує, чи наш друг перебуває в мережі, чи ні.

Тепер ми можемо використовувати цей хук в обох компонентах:

function FriendStatus(props) {
  const isOnline = useFriendStatus(props.friend.id);
  if (isOnline === null) {
    return 'Завантаження...';
  }
  return isOnline ? 'В мережі' : 'Не в мережі';
}
function FriendListItem(props) {
  const isOnline = useFriendStatus(props.friend.id);
  return (
    <li style={{ color: isOnline ? 'green' : 'black' }}>
      {props.friend.name}
    </li>
  );
}

Стани компонентів цілковито незалежні одне від одного. Хуки — це спосіб повторно використовувати логіку стану, а не сам стан. Більш того, кожний виклик хука забезпечує абсолютно ізольований стан. Ви навіть можете використовувати один і той самий хук декілька разів в одному компоненті.

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

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

Докладне пояснення

Ви можете дізнатися більше про користувацькі хуки на сторінці Створення користувацьких хуків.

🔌 Інші хуки

Є ще декілька менш використовуваних вбудованих хуків, що можуть стати вам в пригоді. Наприклад, за допомогою useContext ви можете підписатися на контекст React без використання будь-яких вкладень:

function Example() {
  const locale = useContext(LocaleContext);  const theme = useContext(ThemeContext);  // ...
}

А хук useReducer надає можливість управляти внутрішнім станом більш складного компонента за допомогою редюсера:

function Todos() {
  const [todos, dispatch] = useReducer(todosReducer);  // ...

Докладне пояснення

Ви можете дізнатися більше про вбудовані хуки на сторінці API-довідки хуків.

Що далі?

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

Ви також можете переглянути API-довідник хуків і FAQ хуків.

Нарешті, не проходьте повз вступної сторінки, яка пояснює чому ми додаємо хуки та як ми плануємо використовувати їх разом з класами без необхідності переписувати наші програми.