Олег Марков
Как и зачем использовать 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.
Постройте личный план изучения React до уровня Middle — бесплатно!
React — часть карты развития Frontend
100+ шагов развития
30 бесплатных лекций
300 бонусных рублей на счет
Бесплатные лекции
Все гайды по React
Лучшие курсы по теме

React и Redux Toolkit
Антон Ларичев
TypeScript с нуля
Антон Ларичев