логотип PurpleSchool
логотип PurpleSchool

Как и зачем использовать React Hooks

Автор

Олег Марков

Введение

React Hooks — это механизм, введённый в React 16.8, позволяющий "наделять" функциональные компоненты возможностями, раньше доступными только классам: состоянием, жизненным циклом, доступом к контексту и другим элементам управления жизнью компонента. Hooks (хуки) открыли путь к более отказоустойчивому, предсказуемому и чистому коду, чему способствовала возможность повторного использования логики без классов и наследования.

Использование хуков стало стандартом разработки на React. Поэтому, если вы только переходите от классовых компонентов или начинаете разрабатывать с нуля, важно не только знать, какие бывают хуки, но и понять, зачем они нужны, как их корректно применять и какие подводные камни могут встретиться на пути.

В этой статье я расскажу вам, какие бывают React Hooks, в чем их основное преимущество, как они работают на практике и почему без них современное React-приложение представить сложно. Также мы подробно рассмотрим примеры кода и разберем ситуацию, когда выбор хуков действительно оправдан.

useState — локальное состояние в функциональном компоненте

Одна из наиболее частых задач при построении интерфейсов — хранение и обновление данных внутри компонента: счетчики, тексты в инпутах, флаги показа модалок и многое другое. С помощью хука useState это делается очень просто.

Синтаксис и пример работы

import { useState } from 'react';

function Counter() {
  // Создаем переменную count и функцию setCount для её обновления.
  // Изначальное значение count — 0.
  const [count, setCount] = useState(0);

  // Функция увеличивает count на 1
  const increment = () => setCount(count + 1);

  return (
    <div>
      <p>Текущее значение: {count}</p>
      <button onClick={increment}>Добавить</button>
    </div>
  );
}

Здесь useState(0) возвращает массив из двух элементов: текущее значение (count) и функцию, меняющую это значение (setCount). Такой подход позволяет хранить любое количество простых или сложных переменных состояния внутри одного и того же компонента. Всё, что нужно делать — вызывать setCount с новым значением, чтобы React повторно прорисовал компонент с обновлённым состоянием.

Особенности работы useState

  • Обновление состояния асинхронно и может быть объединено с другими обновлениями, чтобы минимизировать количество ререндеров.
  • Значение состояния можно вычислять динамически, передав функцию:
// setCount принимает функцию, которая получает предыдущее значение
setCount(prev => prev + 1);

Это особенно полезно, когда новое состояние зависит от предыдущего.

useEffect — управление побочными эффектами

В React под "эффектами" обычно понимают любые операции, не связанные напрямую с отрисовкой компонента: запросы к серверу, подписка на события, работа с DOM вне React и др. За это отвечает хук useEffect.

Как работает useEffect

import { useState, useEffect } from 'react';

function Timer() {
  const [seconds, setSeconds] = useState(0);

  useEffect(() => {
    // Запускается при маунте компонента: создаём интервал
    const interval = setInterval(() => {
      setSeconds(prev => prev + 1); // Увеличиваем время каждую секунду
    }, 1000);

    // Очистка интервала при размонтировании компонента
    return () => clearInterval(interval);
  }, []); // [] означает, что эффект запустится только при маунте/размонте
  return <div>Прошло секунд: {seconds}</div>;
}

Здесь вы видите типичный паттерн: создаем интервал, а в функции очистки удаляем его, чтобы избежать утечек памяти.

React Hooks — это функции, которые позволяют использовать состояние и другие возможности React в функциональных компонентах. Они упрощают разработку, делают код более читаемым и переиспользуемым. Если вы хотите научиться использовать React Hooks и узнаете о их преимуществах — приходите на наш большой курс Основы React, React Router и Redux Toolkit. На курсе 177 уроков и 17 упражнений, AI-тренажеры для безлимитной практики с кодом и задачами 24/7, решение задач с живым ревью наставника, еженедельные встречи с менторами.

Особенности и зависимости

  • Массив зависимостей ([] во втором аргументе) определяет, когда эффект будет запускаться.

    • Пустой массив — эффект срабатывает только при маунте и размонтировании.
    • Если указать useEffect(() => {...}, [value]), то эффект выполнится при первом рендере и при каждом изменении value.
  • Если не указать второй аргумент, эффект будет срабатывать после каждого рендера компонента — такой подход используется редко.

Использование для загрузки данных

useEffect(() => {
  fetch('/api/data')
    .then(response => response.json())
    .then(data => setData(data));
}, []); // Опять передаем []: выполняется один раз при маунте

Благодаря этому коду данные из API будут загружены один раз при появлении компонента на странице.

useContext — доступ к контексту без оборачивания в Consumer

Раньше чтобы получить данные из контекста (например, текущий пользователь, языковые настройки и др.) приходилось использовать обертки <Context.Consumer>. Хук useContext резко упростил этот процесс.

Пример с контекстом

import { createContext, useContext } from 'react';

const ThemeContext = createContext('light'); // светлая тема по умолчанию

function ThemeButton() {
  const theme = useContext(ThemeContext); // Получаем доступ к значению контекста

  return (
    <button className={theme}>
      Сменить тему
    </button>
  );
}

Чтобы "пробросить" тему вниз по дереву компонентов, достаточно обернуть в <ThemeContext.Provider>. Смотрите:

<ThemeContext.Provider value="dark">
  <ThemeButton />
</ThemeContext.Provider>

Здесь ThemeButton автоматически узнает, что тема теперь "dark".

useReducer — альтернатива useState для сложной логики

Если в компоненте много состояний, зависящих друг от друга, или требуется реализовать паттерн "redux-подобного" reducer'а, тут лучше использовать useReducer.

Пример с useReducer

import { useReducer } from 'react';

function reducer(state, action) {
  switch(action.type) {
    case 'increment':
      return { count: state.count + 1 };
    case 'decrement':
      return { count: state.count - 1 };
    default:
      return state;
  }
}

function Counter() {
  // useReducer возвращает state и dispatch для отправки экшенов
  const [state, dispatch] = useReducer(reducer, { count: 0 });

  return (
    <div>
      <p>Счетчик: {state.count}</p>
      <button onClick={() => dispatch({ type: 'increment' })}>+</button>
      <button onClick={() => dispatch({ type: 'decrement' })}>-</button>
    </div>
  );
}

useReducer дает вам больше контроля и лучше подходит для управления сложными состояниями, когда одновременное изменение нескольких свойств становится проблемой для обычного useState.

useRef — хранение изменяемых значений и доступ к DOM

Хук useRef используется для хранения изменяемых данных, которые не вызывают ререндер компонента при их изменении (например, таймеры, предыдущие значения, ссылки на DOM-элементы).

Пример с доступом к DOM

import { useRef } from 'react';

function InputFocus() {
  const inputRef = useRef(null); // Создаем ref для хранения ссылки на input

  function handleClick() {
    inputRef.current.focus(); // Фокусируем input программно через ссылку
  }

  return (
    <div>
      <input ref={inputRef} placeholder="Нажмите кнопку для фокуса" />
      <button onClick={handleClick}>Фокус на инпут</button>
    </div>
  );
}

useRef — ваш инструмент для управления "можно изменять, но не хочу вызывать ререндер".

useMemo и useCallback — оптимизация производительности

В сложных приложениях рендеринг тяжелых вычислений или создание одних и тех же функций приводит к ненужным ререндерингам и просадке производительности. Для этого есть два инструмента: useMemo и useCallback.

useMemo

Мемоизирует (кеширует) результат вычислений и возвращает его, пока массив зависимостей не изменится.

import { useMemo, useState } from 'react';

function ExpensiveComponent({ value }) {
  const [counter, setCounter] = useState(0);

  // Здесь тяжелая функция работает только если value изменился
  const computed = useMemo(() => {
    // Имитация тяжелых вычислений
    let result = 0;
    for (let i = 0; i < 10000000; i++) {
      result += value * i;
    }
    return result;
  }, [value]);

  return (
    <div>
      <p>Результат вычислений: {computed}</p>
      <button onClick={() => setCounter(counter + 1)}>Обновить</button>
    </div>
  );
}

useCallback

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

import { useCallback, useState } from 'react';

function Parent() {
  const [value, setValue] = useState(0);

  // useCallback вернет новый handleChange только если value изменится
  const handleChange = useCallback((event) => {
    setValue(Number(event.target.value));
  }, []);

  return (
    <Child onChange={handleChange} value={value} />
  );
}

function Child({ onChange, value }) {
  return <input value={value} onChange={onChange} />;
}

Кастомные хуки — повторное использование вашей логики

React Hooks позволяют создавать свои собственные хуки, чтобы вынести повторяющиеся участки логики (например, обработка формы, отслеживание размера окна, интеграция с API) вне компонента.

Пример создания кастомного хука

import { useState, useEffect } from 'react';

// Кастомный хук для отслеживания ширины окна
function useWindowWidth() {
  const [width, setWidth] = useState(window.innerWidth);

  useEffect(() => {
    function handleResize() {
      setWidth(window.innerWidth);
    }

    window.addEventListener('resize', handleResize);

    // Очистка подписки при размонтировании
    return () => window.removeEventListener('resize', handleResize);
  }, []);

  return width;
}

function MyComponent() {
  const width = useWindowWidth(); // теперь вы всегда знаете ширину экрана

  return <div>Текущая ширина окна: {width}px</div>;
}

Такой подход помогает не только переиспользовать код, но и разделять ответственность между компонентами.

Как и зачем использовать React Hooks в реальных проектах

Давайте теперь разберёмся, почему хуки стали стандартом современной разработки на React и какие задачи они решают лучше, чем классические подходы.

Причины использовать хуки

  • Упрощение структуры компонентов. Отказ от классов избавляет от сложных this, bind, наследования.
  • Разделение логики по признаку принадлежности. Вместо огромных компонентов с методами жизненного цикла и состояниями, вы можете собирать логику по смыслу — через кастомные хуки.
  • Переиспользование функциональности. Хуки можно повторно использовать в любых компонентах.
  • Меньше бойлерплейта. Меньше кода для того же результата.
  • Лучшее понимание зависимостей. Благодаря декларативному паттерну и четким зависимостям внутри хуков.

Когда хуки не нужны

Бывают случаи, когда хуки — не лучшее решение. Например, если у вас очень простое статичное приложение без логики, или вы поддерживаете устаревший код на классах. Всё же в большинстве ситуаций хуки сильно упрощают жизнь разработчика.

Общие рекомендации по использованию хуков

  • Не используйте хуки внутри условных операторов или циклов.
  • Не вызывайте хуки вне тела функционального компонента или другого хука.
  • Используйте eslint-plugin-react-hooks для проверки корректности написания хуков.

Советы и частые ошибки

  • Ошибка: Хуки вызываются условно

    • Неправильно: jsx if (someCondition) { useEffect(() => { /* что-то */ }, []); }
    • Правильно: хуки всегда должны быть на одном уровне и в одном порядке при каждом рендере.
  • Ошибка: Хуки вызываются вне компонента

    • Это приводит к тому, что React просто не знает, как управлять их состоянием.
  • Ошибка: Не указаны зависимости

    • Если не указать зависимости для useEffect/useMemo/useCallback, возможны баги: логику нужно пересматривать.
  • Совет: используйте кастомные хуки для "грязной" логики

    • Например, отслеживание мыши, состояния формы — всё это удобно заворачивать в кастомные хуки для удобства и повторного использования.

Заключение

React Hooks — это мощный инструмент, который позволяет управлять состоянием, эффектами, подписками и другой логикой напрямую внутри функциональных компонентов. Они упрощают структуру приложения, делают код более читаемым и переиспользуемым, позволяют делить логику между компонентами не прибегая к наследованию.

Благодаря хукам появляется новый стиль проектирования: вместо "монолитных" классовых компонентов вы строите композиции из маленьких, управляемых сущностей (хуков и компонентов). Если вы хотите писать на современном React, использование хуков практически необходимо. Главное — всегда помнить об их правилах использования и тщательно следить за зависимостями внутри эффектов и мемоизации.

React Hooks значительно упрощают разработку. Для создания сложных приложений требуется умение управлять состоянием всего приложения и организовывать навигацию. Рассмотрите курс Основы React, React Router и Redux Toolkit для получения необходимых навыков. В первых 3 модулях уже доступно бесплатное содержание — начните погружаться в основы React уже сегодня.

Частозадаваемые технические вопросы по теме статьи и ответы на них

Как обрабатывать асинхронные операции внутри useEffect?

Внутри useEffect использовать async/await напрямую нельзя. Вместо этого создайте асинхронную функцию внутри эффекта и вызовите её:

useEffect(() => {
  async function fetchData() {
    const res = await fetch('/api/data');
    // обработка данных
  }
  fetchData();
}, []);

Как сбросить или очистить таймер, созданный через setInterval в useEffect?

Сохраняйте id таймера в переменной и вызывайте clearInterval в функции очистки (return). Так вы избежите утечки ресурсов.

useEffect(() => {
  const id = setInterval(() => { /* ... */ }, 1000);
  return () => clearInterval(id);
}, []);

Можно ли использовать хуки внутри циклов или условий?

Нет. Согласно правилам React, все хуки должны вызываться строго на верхнем уровне компонента и всегда в одном порядке при каждом рендере.

Как использовать Redux вместе с хуками?

C Redux Toolkit можно задействовать хуки useSelector и useDispatch для доступа к состоянию и отправки экшенов:

const dispatch = useDispatch();
const value = useSelector(state => state.someValue);

Как тестировать компоненты с хуками?

Используйте react-testing-library: она позволяет рендерить компонент, изменять значения пропсов и отслеживать, как меняются состояния и эффекты в хуках. Если вы тестируете кастомный хук, используйте хелпер renderHook из библиотеки @testing-library/react-hooks.

Стрелочка влевоЧто такое useRef и как его применять в ReactУправление состоянием в React через ContextСтрелочка вправо

Постройте личный план изучения React до уровня Middle — бесплатно!

React — часть карты развития Frontend

  • step100+ шагов развития
  • lessons30 бесплатных лекций
  • lessons300 бонусных рублей на счет

Бесплатные лекции

Все гайды по React

Открыть базу знаний

Лучшие курсы по теме

изображение курса

React и Redux Toolkit

Антон Ларичев
AI-тренажеры
Практика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.8
3 999 ₽ 6 990 ₽
Подробнее
изображение курса

TypeScript с нуля

Антон Ларичев
AI-тренажеры
Практика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.7
3 999 ₽ 6 990 ₽
Подробнее
изображение курса

Next.js - с нуля

Антон Ларичев
AI-тренажеры
Практика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.7
3 999 ₽ 6 990 ₽
Подробнее

Отправить комментарий