React Hooks — що це, основні хуки та правила використання

Автор: IT Sectr Опубліковано: 2026-07-04 Час читання: 10 хв

React Hooks — це функції, які дозволяють використовувати стан, ефекти, контекст та інші можливості React в функціональних компонентах, без написання класів. Представлені в React 16.8, хуки радикально змінили підхід до розробки, замінивши складні патерни на кшталт HOC та render props. За даними React, 2024, існує 15 вбудованих хуків, що покривають усі сценарії управління компонентами.

Головне

  • Хуки — функції для підключення можливостей React до функціональних компонентів без extends Component.
  • useState — базовий хук для керування станом, повертає значення та сетер.
  • useEffect — для побічних ефектів: запити API, підписки, таймери, маніпуляції DOM.
  • useContext — для доступу до контексту React без Consumer-обгорток та static contextType.
  • Правила хуків — викликати тільки на верхньому рівні та лише всередині функціональних компонентів або кастомних хуків.

Що таке хуки React?

React Hooks — це набір функцій, вбудованих в React, які дозволяють «підключатися» до внутрішніх механізмів фреймворку без створення класів. До React 16.8 функціональні компоненти могли лише приймати props та повертати JSX — будь-яка складна логіка вимагала класового компонента.

Проблема класових компонентів — розділення пов’язаної логіки. Код, пов’язаний з однією сутністю (наприклад, підписка на WebSocket), був вимушено розділений між componentDidMount, componentDidUpdate та componentWillUnmount. Хуки вирішують це: уся логіка групується в одному useEffect.

Друга проблема — повторне використання логіки. Щоб повторно використати логіку стану між класовими компонентами, потрібно було застосовувати HOC (компоненти вищого порядку) або render props — обидва патерни створювали зайву вкладеність в дереві компонентів. Кастомні хуки вирішують цю проблему без вкладеності.

useState — Керування станом

useState — найбазовіший хук. Він приймає початкове значення та повертає кортеж: поточне значення та функцію для його оновлення. На відміну від this.setState у класах, useState не об’єднує об’єкти — новий стан повністю замінює попередній для конкретного виклику useState.

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

function Form() {
  const [text, setText] = useState('');
  const [submitting, setSubmitting] = useState(false);

  async function handleSubmit() {
    setSubmitting(true);
    await fetch('/api/submit', {
      method: 'POST',
      body: JSON.stringify({ text })
    });
    setSubmitting(false);
  }

  return (
    <div>
      <input
        value={text}
        onChange={e => setText(e.target.value)}
      />
      <button onClick={handleSubmit}
        disabled={submitting}>
        {submitting ? 'Saving...' : 'Submit'}
      </button>
    </div>
  );
}

Функціональна форма setState

Сетер useState може приймати функцію, яка отримує попереднє значення та повертає нове. Функціональна форма обов’язкова, коли новий стан обчислюється з попереднього та можливі пакетні виклики setState — кожний виклик отримає поточне попереднє значення.

useEffect — Побічні ефекти та життєвий цикл

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

Порожній масив залежностей [] означає, що ефект виконується один раз після першого рендеру (аналог componentDidMount). Повернення функції з ефекту — це очищення, яке виконується при розмонтуванні (аналог componentWillUnmount) та перед повторним виконанням ефекту.

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

function ChatRoom({ roomId }) {
  const [messages, setMessages] = useState([]);

  useEffect(() => {
    const socket = new WebSocket(`wss://chat/${roomId}`);

    socket.onmessage = (event) => {
      setMessages(prev => [...prev, JSON.parse(event.data)]);
    };

    // Очищення - закрити сокет
    return () => socket.close();
  }, [roomId]);

  return <ul>
    {messages.map((msg, i) =>
      <li key={i}>{msg.text}</li>
    )}
  </ul>;
}

Залежності useEffect — це єдиний зв’язок між ефектом та зовнішнім світом. Якщо в ефекті використовується змінна з props або стану, вона повинна бути в масиві залежностей. Ігнорування цього правила є джерелом помилок зі застарілими замиканнями (stale closures).

useContext — Доступ до контексту

useContext — хук для читання значення контексту. Він приймає об’єкт контексту, створений за допомогою React.createContext, та повертає поточне значення. Якщо контекст змінюється, компонент автоматично перерендерюється з новим значенням.

До появи useContext доступ до контексту в функціональних компонентах потребував обгортки в Context.Consumer, що створювало вкладеність. useContext повністю усуває Consumer — достатьньо викликати хук в будь-якому місці функціонального компонента.

js
import React, { createContext, useContext } from 'react';

const ThemeContext = createContext('світлий');

function ThemedButton() {
  const theme = useContext(ThemeContext);

  return <button
    className={`btn btn-${theme}`}
  >
    {theme === 'dark' ? 'moon' : '☀️'}
  </button>;
}

useContext — це не заміна Redux або глобального стану. React рекомендує використовувати контекст для рідко змінюваних значень — теми, локалі, статус авторизації. Для часто оновлюваних даних кожен перерендер переобчислюватиме всіх споживачів контексту, що може знизити продуктивність.

useReducer — Складна логіка стану

useReducer — альтернатива useState для станів зі складною логікою оновлення. Він приймає функцію-редюсер та початковий стан, повертає поточний стан та функцію dispatch. Редюсер отримує поточний стан та дію, повертає новий стан — чиста функція без побічних ефектів.

useReducer особливо корисний, коли кілька дій (set, reset, increment, addItem) керують одним станом. Замість кількох useState з різними сетерами — один редюсер з чіткими типами дій. Це робить логіку предбачуваною та легко тестованою.

js
import React, { useReducer } from 'react';

function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return { ...state, count: state.count + 1 };
    case 'setStep':
      return { ...state, step: action.step };
    case 'reset':
      return { count: 0, step: 1 };
    default:
      throw new Error('Unknown action: ' + action.type);
  }
}

function Counter() {
  const [state, dispatch] = useReducer(reducer, {
    count: 0,
    step: 1
  });

  return (
    <>
      <p>Count: {state.count}</p>
      <button
        onClick={() => dispatch({ type: 'increment' })}>+</button>
      <button
        onClick={() => dispatch({ type: 'reset' })}>Reset</button>
    </>
  );
}

useRef, useMemo, useCallback — Оптимізація

useRef створює змінний об’єкт, який зберігається між рендерами. Зміна current не викликає перерендер. Основне застосування — доступ до елементів DOM та зберігання мутабельних значень (таймери, попередні значення, екземпляри бібліотек).

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

js
import React, { useRef, useMemo, useCallback } from 'react';

function SearchPage({ items, query, onItemSelect }) {
  const inputRef = useRef(null);

  // Автофокус при монтуванні
  useEffect(() => {
    inputRef.current?.focus();
  }, []);

  // Замемоізувати відфільтрований результат
  const filtered = useMemo(() => {
    return items.filter(item =>
      item.name.toLowerCase()
        .includes(query.toLowerCase())
    );
  }, [items, query]);

  // Замемоізувати колбек
  const handleSelect = useCallback((id) => {
    onItemSelect(id);
  }, [onItemSelect]);

  return (
    <div>
      <input ref={inputRef}
        value={query}
        onChange={e => setQuery(e.target.value)}
      />
      <ul>
        {filtered.map(item =>
          <li key={item.id}>
            <button
              onClick={() => handleSelect(item.id)}>
              {item.name}
            </button>
          </li>
        )}
      </ul>
    </div>
  );
}

Важливе правило: useRef, useMemo та useCallback — це інструменти оптимізації, а не обов’язкові конструкції. Не обгортайте все в useMemo — спочатку виміряйте продуктивність. Передчасна оптимізація ускладнює код. React рендерить компоненти швидко; useMemo потрібен лише коли обчислення дійсно важкі (O(n²) та вище).

Правила хуків та часті помилки

React запровадив два жорсткі правила для хуків. Перше: викликайте хуки лише на верхньому рівні — не всередині умов, циклів або вкладених функцій. Друге: викликайте хуки лише з функціональних компонентів React або кастомних хуків. ESLint-плагін eslint-plugin-react-hooks автоматично перевіряє обидва правила.

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

ПомилкаЩо відбуваєтьсяРішення
Хук в умовіПорядок хуків порушуєтьсяПеренесіть логіку умови всередину хука
Stale closureЕфект використовує старе значенняДодайте всі залежності в масив
Нескінченний циклuseEffect без залежностей змінює станЗазначте залежності або використовуйте useReducer
Зміна ref в renderПобічний ефект в тілі функціїВиносьте мутації в useEffect

Друга часта помилка — stale closure в useEffect. Якщо в ефекті використовується змінна, не зазначена в залежностях, ефект «захоплює» її значення на момент створення замикання. Рішення — завжди включайте всі використані змінні в масив залежностей. Якщо залежностей забагато, розгляньте можливість розділу ефекту на кілька.

Часто задавані питання

Чому хуки не можна викликати в умовах?

React покладається на порядок виклику хуків між рендерами. Якщо хук викликається всередині умови та не спрацювує на одному з рендерів, усі наступні хуки зсуваються — їхній стан перемішується. Правило ESLint hooks/exhaustive-deps попереджає про це.

В чому різниця між useEffect та useLayoutEffect?

useLayoutEffect синхронний і виконується до того, як браузер намалює зміни. useEffect асинхронний — після відмалювання. Використовуйте useLayoutEffect лише коли потрібно виміряти або змінити DOM до того, як користувач побачить зміни (popover позиція, відновлення прокрутки).

Як скасувати fetch-запит при розмонтуванні?

Використовуйте AbortController всередині useEffect. Створіть controller, передайте signal в fetch, і викличіть controller.abort() в функції очищення. Для старих браузерів використовуйте прапорець cancelled: return () => { cancelled = true; } з перевіркою перед setState.

Що таке кастомний хук і коли його створювати?

Кастомний хук — це функція JavaScript з префіксом use, яка використовує вбудовані хуки. Створюйте його, коли одна й та сама логіка з хуками повторюється в двох або більше компонентах. Приклади: useDebounce, useLocalStorage, useMediaQuery. Кастомні хуки замінюють HOC та render props.

Чому useEffect виконується двічі в Strict Mode?

В Strict Mode React навмисно монтує та демонтує компонент двічі в режимі розробки, щоб знайти помилки з неочищеними ефектами. Якщо очищення написано правильно, подвійний виклик не викликає проблем. У виробничому режимі цього не відбувається.

Підсумки

  • React Hooks — функції для використання стану, ефектів та контексту в функціональних компонентах, що замінили класи.
  • useState — базовий хук для локального стану з функціональним сетером для безпечних оновлень.
  • useEffect — універсальний хук для побічних ефектів з масивом залежностей та функцією очищення.
  • useContext — прямий доступ до контексту React без Consumer-обгорток та зайвої вкладеності.
  • useReducer — для складної логіки стану з типами дій, альтернатива Redux в межах одного компонента.
  • useRef, useMemo, useCallback — інструменти оптимізації: посилання на DOM, мемоізація значень та колбеків.
  • Правило верхнього рівня — суворе обмеження на виклик хуків поза умовами, циклами та вкладеними функціями.

Ми розробимо мобільний застосунок під ключ

IT Sectr створює застосунки для iOS та Android для стартапів і бізнесу з 2017 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.

Обговорити проект

Читайте також