Хуки — то, чем компонент остаётся про отображение: логику выносят в хуки, а компонент их вызывает. Большинство хуков просты, а вот useEffect — источник самых частых багов на frontend, потому что его применяют там, где он не нужен. Понять хуки — значит уметь извлекать логику и не злоупотреблять эффектами.
Правила хуков
Два правила, нарушение которых ломает React. Хуки вызывают только (1) на верхнем уровне компонента или другого хука — не в условиях, циклах, вложенных функциях; (2) из React-функций — компонентов и кастомных хуков, не из обычных функций.
// неверно: хук под условием — порядок вызовов «поплывёт»
if (open) { const [x, setX] = useState(0); }
// верно: хук на верхнем уровне, условие внутри
const [x, setX] = useState(0);
if (open) { /* используем x */ }
React опирается на стабильный порядок вызовов хуков между рендерами — отсюда правила. Линтер (eslint-plugin-react-hooks) ловит нарушения; его держат включённым.
Кастомные хуки
Кастомный хук — функция с именем use..., которая собирает логику из других хуков. Это способ переиспользовать логику без дублирования и без композиции компонентов ради одной логики.
function useDebounced<T>(value: T, delayMs: number): T {
const [debounced, setDebounced] = useState(value);
useEffect(() => {
const id = setTimeout(() => setDebounced(value), delayMs);
return () => clearTimeout(id);
}, [value, delayMs]);
return debounced;
}
function Search() {
const [query, setQuery] = useState("");
const debouncedQuery = useDebounced(query, 300);
const { data } = useSearch(debouncedQuery);
// ...
}
Логика дебаунса вынесена и переиспользуема; компонент остаётся про вид. Кастомные хуки компонуются — один зовёт другой, как useSearch мог бы звать TanStack Query.
Ловушки useEffect
useEffect синхронизирует компонент с внешним миром (подписки, таймеры, ручная работа с DOM). Три ловушки.
Зависимости. Массив зависимостей перечисляет всё, что эффект читает; забыл — эффект работает на устаревших значениях. Линтер подсказывает, и его правке стоит доверять, а не «затыкать» массив вручную.
Гонки. Если эффект грузит данные, быстрый повтор даёт устаревший ответ поверх свежего. Защита — флаг отмены в очистке:
useEffect(() => {
let cancelled = false;
fetchUser(id).then((u) => { if (!cancelled) setUser(u); });
return () => { cancelled = true; };
}, [id]);
Очистка. Подписки, таймеры, слушатели закрывают в функции, возвращаемой из эффекта, — иначе утечки.
Когда useEffect НЕ нужен
Самое важное: большинство useEffect, которые пишут новички, не нужны. Признаки лишнего эффекта:
- Производное значение — не эффект + state, а вычисление при рендере:
const fullName = first + " " + last;(неuseEffect, обновляющийfullName). - Реакция на событие — логика идёт в обработчик события, не в эффект, реагирующий на изменение состояния.
- Загрузка серверных данных — это TanStack Query, а не
useEffect+useState(см. ловушку гонок выше — Query решает её сам).
Правило: прежде чем писать useEffect, спроси, не вычисляется ли это при рендере и не место ли этому в обработчике. Эффект — для синхронизации с внешним миром, не для реакции на собственное состояние.
Где это в UCP
Хуки — это организация логики frontend: правила держат React предсказуемым, кастомные хуки выносят и переиспользуют логику (как Handler выносит сценарий из контроллера), а дисциплина «useEffect только для внешней синхронизации» убирает целый класс багов. Для продукт-инженера это меньше скрытых ошибок и более читаемые компоненты — а то, каким состоянием хуки управляют, разобрано отдельно.