React Hooks — что это, основные хуки и правила использования

Автор: IT Sectr Опубликовано: 2026-07-04 Время чтения: 10 мин

Хуки React — это функции, которые позволяют использовать состояние, эффекты, контекст и другие возможности React в функциональных компонентах, без написания классов. Представленные в React 16.8, хуки радикально изменили подход к разработке, заменив сложные паттерны вроде HOC и render props. По данным React, 2024, существует 15 встроенных хуков, покрывающих все сценарии управления компонентом.

Главное

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

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

Хуки React — набор функций, встроенных в 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). Возврат функции из эффекта — это cleanup, который выполняется при размонтировании (аналог 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)]);
    };

    // Cleanup - close socket
    return () => socket.close();
  }, [roomId]);

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

Зависимости useEffect — это единственная связь между эффектом и внешним миром. Если в эффекте используется переменная из пропсов или состояния, она должна быть в массиве зависимостей. Игнорирование этого правила — источник багов с устаревшими данными (stale closure).

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

useContext — хук для чтения значения контекста. Он принимает объект контекста, созданный React.createContext, и возвращает текущее значение. Если контекст меняется, компонент автоматически ререндерится с новым значением.

До появления useContext доступ к контексту в функциональных компонентах требовал обёртки в Context.Consumer, что создавало вложенность. useContext полностью устраняет Consumer — достаточно вызвать хук в любом месте функционального компонента.

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

const ThemeContext = createContext('light');

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

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

useContext — это не замена Redux или глобальному стейту. React рекомендует использовать контекст для редко изменяемых значений — тема, локаль, авторизация. Для часто обновляемых данных каждый ререндер будет заново вычислять всех потребителей контекста, что может снизить производительность.

useReducer — сложная логика состояния

useReducer — альтернатива useState для состояний со сложной логикой обновления. Он принимает reducer-функцию и начальное состояние, возвращает текущее состояние и dispatch-функцию. Reducer получает текущее состояние и action, возвращает новое состояние — чистая функция без побочных эффектов.

useReducer особенно полезен, когда несколько действий (set, reset, increment, addItem) управляют одним состоянием. Вместо нескольких useState с разными сеттерами — один reducer с чёткими action-типами. Это делает логику предсказуемой и легко тестируемой.

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);

  // Autofocus on mount
  useEffect(() => {
    inputRef.current?.focus();
  }, []);

  // Memoize filtered result
  const filtered = useMemo(() => {
    return items.filter(item =>
      item.name.toLowerCase()
        .includes(query.toLowerCase())
    );
  }, [items, query]);

  // Memoize callback
  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 без зависимостей меняет stateУкажите зависимости или используйте useReducer
Изменение ref в renderПобочный эффект в теле функцииВыносите мутации в useEffect

Вторая частая ошибка — stale closure в useEffect. Если в эффекте используется переменная, не указанная в зависимостях, эффект «захватывает» её значение на момент создания замыкания. Решение — всегда включать все используемые переменные в массив зависимостей. Если зависимостей слишком много — возможно, стоит разделить эффект на несколько.

Часто задаваемые вопросы

Почему хуки нельзя вызывать в условиях?

React полагается на порядок вызова хуков между рендерами. Если хук вызван в условии и не сработал на одном из рендеров, все последующие хуки «съезжают» — их состояние перемешивается. ESLint-правило hooks/exhaustive-deps предупреждает об этом.

В чём разница между useEffect и useLayoutEffect?

useLayoutEffect синхронный и выполняется до того, как браузер отрисует изменения. useEffect асинхронный — после отрисовки. Используйте useLayoutEffect только когда нужно измерить или изменить DOM до того, как пользователь увидит изменения (popover position, scroll restoration).

Как отменить fetch-запрос при размонтировании?

Используйте AbortController внутри useEffect. Создайте controller, передайте signal в fetch, и в cleanup-функции вызовите controller.abort(). Для старых браузеров подойдёт флаг cancelled: return () => { cancelled = true; } с проверкой перед setState.

Что такое кастомный хук и когда его создавать?

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

Почему useEffect выполняется дважды в Strict Mode?

В Strict Mode React намеренно монтирует и размонтирует компонент дважды в development, чтобы найти ошибки с неочищенными эффектами. Если cleanup написан корректно, двойной вызов не вызывает проблем. В production режиме это не происходит.

Итоги

  • Хуки React — функции для использования состояния, эффектов и контекста в функциональных компонентах, заменившие классы.
  • useState — базовый хук для локального состояния с функциональным сеттером для безопасных обновлений.
  • useEffect — универсальный хук для побочных эффектов с массивом зависимостей и cleanup-функцией.
  • useContext — прямой доступ к контексту React без Consumer-обёрток и лишней вложенности.
  • useReducer — для сложной логики состояния с action-типами, альтернатива Redux в рамках одного компонента.
  • useRef, useMemo, useCallback — инструменты оптимизации: ссылки на DOM, мемоизация значений и колбэков.
  • Правило верхнего уровня — строгое ограничение на вызов хуков вне условий, циклов и вложенных функций.

Мы разработаем мобильное приложение под ключ

IT Sectr создаёт приложения для iOS и Android для стартапов и бизнеса с 2017 года. Мы проконсультируем вас и предложим наилучшее решение.

Обсудить проект

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