Огляд хуків

Хуки — новинка у 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 пропускав повторні підписки, якщо 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 хуків.

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