Полное руководство по хукам React: useState, useEffect, useMemo и useCallback
Практическое руководство по хукам React 18/19: управление состоянием, жизненный цикл, предотвращение лишних ререндеров и разбор частых ошибок с зависимостями.
Модуль 14: Хуки React на практике
Пишите реальные компоненты с хуками и проверяйте работу стейта через интерактивные тесты.
Хуки появились в React 16.8 и полностью изменили разработку компонентов: они позволяют использовать локальное состояние, побочные эффекты и оптимизацию внутри функциональных компонентов без написания классов.
1. Фундаментальные правила хуков
Прежде чем писать код, необходимо соблюдать два строгих соглашения React:
Два золотых правила хуков:
- Вызывайте хуки только на самом верхнем уровне: не вызывайте хуки внутри циклов (
for), условных операторов (if) или вложенных функций. Порядок вызова хуков обязан быть строго идентичным при каждом рендере.- Вызывайте хуки только из функций React: из функциональных компонентов или ваших собственных кастомных хуков.
2. useState: Управление локальным состоянием
Хук useState сохраняет значение между перерисовками компонента и предоставляет функцию обновления:
import { useState } from 'react';
export function Counter() {
const [count, setCount] = useState<number>(0);
const increment = () => {
// Безопасное обновление через коллбэк, если новое значение зависит от предыдущего:
setCount(prev => prev + 1);
};
return (
<button onClick={increment} className="btn-primary">
Количество кликов: {count}
</button>
);
}
Совет эксперта:
Если начальное состояние требует тяжелых вычислений (например, чтение и парсинг изlocalStorage), передавайте функцию инициализатора:
const [data, setData] = useState(() => loadExpensiveData());
Функция выполнится ровно один раз при монтировании, а не при каждом рендере.
3. useEffect: Побочные эффекты и синхронизация
useEffect выполняет сетевые запросы, подписки на внешние хранилища, таймеры и манипуляции с DOM:
import { useState, useEffect } from 'react';
export function UserProfile({ userId }: { userId: string }) {
const [user, setUser] = useState<{ name: string } | null>(null);
useEffect(() => {
let isCancelled = false;
async function loadData() {
const res = await fetch(`/api/users/${userId}`);
const data = await res.json();
if (!isCancelled) {
setUser(data);
}
}
loadData();
// Функция очистки (cleanup) вызывается перед размонтированием или повторным запуском эффекта
return () => {
isCancelled = true;
};
}, [userId]); // Эффект перезапустится только при изменении userId
return <div>{user ? user.name : 'Загрузка...'}</div>;
}
Режимы массива зависимостей
| Массив зависимостей | Когда срабатывает эффект | Сценарий использования |
|---|---|---|
Не указан (useEffect(fn)) | После каждого рендера компонента | Логирование, отслеживание любых перерисовок |
Пустой массив ([]) | Ровно один раз при монтировании | Подписка на глобальные события окна, разовый fetch |
С зависимостями ([id, filter]) | При монтировании и при смене указанных переменных | Загрузка данных под конкретный ID, пересчет фильтров |
Анатомия сбоя:
Самая частая причина бесконечного цикла рендеров — передача нестабильного объекта или стрелочной функции в массив зависимостей без мемоизации. React сравнивает зависимости по ссылке (Object.is), поэтому объект, созданный прямо в теле функции, всегда считается «новым».
4. useMemo и useCallback: Производительность без фанатизма
Оба хука предназначены для мемоизации (кэширования), но работают с разными объектами:
| Критерий | useMemo | useCallback |
|---|---|---|
| Что возвращает | Результат вычисления функции | Ссылку на саму функцию |
| Главная цель | Не пересчитывать тяжелые алгоритмы | Не менять ссылку на функцию между рендерами |
| Типовой пример | Фильтрация и сортировка 10 000 строк | Передача колбэка в дочерний компонент с React.memo |
// useMemo кэширует вычисленное значение:
const sortedProducts = useMemo(() => {
return products.filter(p => p.inStock).sort((a, b) => b.price - a.price);
}, [products]);
// useCallback кэширует неизменную ссылку на функцию:
const handleSelect = useCallback((id: string) => {
console.log('Выбран элемент:', id);
}, []);
Совет эксперта:
Не оборачивайте каждую функцию подряд вuseCallback. Сам по себе хук требует ресурсов процессора и памяти. Мемоизация оправдана только при замерах или передаче колбэка в тяжелые компоненты, защищенные черезReact.memo.
5. Резюме: Чек-лист чистых хуков
- Всегда объявляйте хуки на верхнем уровне функции.
- Всегда возвращайте функцию очистки (
cleanup) изuseEffect, если создаете таймер или слушатель событий. - Включайте все переменные из области видимости компонента в массив зависимостей.
- Оптимизируйте производительность через
useMemoиuseCallbackосознанно.
Понравилась шпаргалка?
В платформе CopyPastWorld собрано более 400 интерактивных уроков с практическими заданиями, автотестами и поддержкой сообщества.
