Антон Ларичев

Введение
React Hooks появились в версии 16.8 и кардинально изменили подход к написанию компонентов. Они позволяют использовать состояние и другие возможности React без классов, делая код короче, понятнее и легче для переиспользования. В этом гайде разберём четыре фундаментальных хука: useState, useEffect, useCallback и useMemo. Эти хуки покрывают около 80% повседневных задач при разработке на React.
Правила использования хуков просты, но обязательны: вызывать их только на верхнем уровне функционального компонента и только из React-функций. Нельзя вызывать хуки внутри условий, циклов или вложенных функций — React полагается на порядок их вызова между рендерами.
useState — управление локальным состоянием
useState — самый базовый хук. Он возвращает массив из двух элементов: текущего значения состояния и функции для его обновления.
import { useState } from 'react';
function Counter() {
// начальное значение состояния — 0
const [count, setCount] = useState(0);
return (
<div>
<p>Счётчик: {count}</p>
<button onClick={() => setCount(count + 1)}>+1</button>
</div>
);
}
Если новое значение зависит от предыдущего, используйте функциональную форму обновления — это безопаснее при асинхронных вызовах:
// безопасное обновление на основе предыдущего значения
setCount(prev => prev + 1);
Для тяжёлой инициализации передавайте функцию, а не значение — она выполнится только при первом рендере:
// ленивая инициализация
const [data, setData] = useState(() => computeExpensiveValue());
useEffect — побочные эффекты
useEffect запускает побочные эффекты после рендера: запросы к API, подписки, работу с DOM, таймеры. Второй аргумент — массив зависимостей, контролирующий повторный запуск эффекта.
import { useState, useEffect } from 'react';
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
useEffect(() => {
let cancelled = false;
// загружаем данные пользователя
fetch(`/api/users/${userId}`)
.then(res => res.json())
.then(data => {
if (!cancelled) setUser(data);
});
// функция очистки предотвращает гонки и утечки
return () => {
cancelled = true;
};
}, [userId]);
if (!user) return <p>Загрузка...</p>;
return <p>{user.name}</p>;
}
Варианты массива зависимостей:
[]— эффект выполнится один раз после монтирования.[a, b]— выполнится при измененииaилиb.- Отсутствует — выполнится после каждого рендера (почти всегда плохо).
useCallback — мемоизация функций
useCallback возвращает мемоизированную версию функции, которая меняется только при изменении зависимостей. Это критично при передаче колбэков в дочерние компоненты, обёрнутые в React.memo.
import { useState, useCallback } from 'react';
function TodoList({ items }) {
const [filter, setFilter] = useState('');
// функция пересоздаётся только при изменении filter
const handleDelete = useCallback((id) => {
console.log('Удаление элемента', id, 'фильтр:', filter);
}, [filter]);
return items.map(item => (
<TodoItem key={item.id} item={item} onDelete={handleDelete} />
));
}
Без useCallback функция handleDelete создавалась бы заново при каждом рендере, и React.memo у дочернего компонента терял бы смысл.
useMemo — мемоизация значений
useMemo кэширует результат вычисления и пересчитывает его только при изменении зависимостей. Применяйте для дорогих вычислений или для сохранения ссылочной идентичности объектов и массивов.
import { useMemo, useState } from 'react';
function ProductList({ products }) {
const [query, setQuery] = useState('');
// фильтрация выполнится только при изменении products или query
const filtered = useMemo(() => {
return products.filter(p =>
p.name.toLowerCase().includes(query.toLowerCase())
);
}, [products, query]);
return (
<ul>
{filtered.map(p => <li key={p.id}>{p.name}</li>)}
</ul>
);
}
Ключевое отличие от useCallback: useMemo мемоизирует возвращаемое значение, а useCallback — саму функцию. По сути, useCallback(fn, deps) эквивалентен useMemo(() => fn, deps).
Частые ошибки
Пропуск зависимостей в useEffect. Если внутри эффекта используется переменная из замыкания, она обязана быть в массиве зависимостей. Иначе вы получите устаревшие данные. Установите ESLint-плагин eslint-plugin-react-hooks — он подсветит такие случаи.
Преждевременная оптимизация useCallback и useMemo. Эти хуки не бесплатны: React тратит память на хранение мемоизированных значений и сравнивает зависимости при каждом рендере. Применяйте их только тогда, когда есть измеримая проблема производительности или ссылочная идентичность реально нужна (например, в зависимостях другого хука).
Мутация состояния напрямую. React сравнивает значения по ссылке. Если вы измените массив через push, компонент не перерендерится. Создавайте новые объекты и массивы: setItems([...items, newItem]).
Отсутствие функции очистки в useEffect. Подписки, таймеры и асинхронные запросы должны отменяться при размонтировании компонента или изменении зависимостей. Иначе получите утечки памяти и обновление состояния у несуществующего компонента.
Вызов хуков внутри условий. Любое условное выполнение хука сломает порядок их вызова и приведёт к ошибкам React. Условие нужно помещать внутрь хука, а не вокруг него.
Заключение
Четыре хука — useState, useEffect, useCallback и useMemo — это основа повседневной работы с React. Состояние и эффекты применяйте смело, а мемоизацию подключайте осознанно, после профилирования. Подружитесь с правилами хуков и линтером — и большинство проблем уйдёт само собой. Освоив этот фундамент, вы готовы перейти к более продвинутым хукам: useReducer, useRef, useContext и кастомным хукам.






Комментарии
0