React

Свои хуки и правила хуков

Десять компонентов подписываются на статус сети, и в каждом одна и та же связка: useState под флаг online, useEffect с подпиской на события online и offline, функция очистки. Копировать этот блок десять раз - значит десять раз чинить один и тот же баг при правке. React даёт способ вынести такую логику с состоянием в одну переиспользуемую функцию useOnlineStatus, и компоненты получают только результат. Так устроены все хуки-библиотеки, на которых стоит современный фронтенд.

  • useOnlineStatus, useLocalStorage, useDebounce - типовые свои хуки, которые есть почти в каждом проекте
  • TanStack Query: useQuery и useMutation - это свои хуки, инкапсулирующие запрос, кеш и состояние загрузки
  • react-hook-form: useForm и useController прячут всю логику управления полями за одним вызовом хука
  • Библиотеки вроде usehooks и ahooks отдают десятки готовых хуков: useInterval, useMediaQuery, useCopyToClipboard
  • Команды выносят бизнес-логику в свои хуки (useCart, useAuth), чтобы переиспользовать её между экранами без дублирования

Предварительные знания

  • useState и useEffect: хранение состояния и синхронизация с внешним миром
  • Функции и замыкания в JavaScript: функция может возвращать значения и захватывать переменные
  • Понимание того, что компонент - это функция, вызываемая React при каждом рендере

Вынос логики в свой хук

Свой хук - это обычная функция, имя которой начинается с use и которая внутри вызывает другие хуки (useState, useEffect и так далее). Его задача - собрать повторяющуюся логику с состоянием в одном месте, чтобы разные компоненты могли её переиспользовать. Компонент вызывает свой хук как любой встроенный и получает готовый результат, не зная про детали реализации - подписки, очистку, обработку событий.

Хороший свой хук скрывает детали и отдаёт понятный результат: значение, объект или кортеж. useOnlineStatus отдаёт булево, useLocalStorage - пару [value, setValue]. Имя хука должно описывать назначение, а не реализацию: useOnlineStatus лучше, чем useWindowEventListeners.

Что делает обычную функцию своим хуком React?

Правила хуков

Хуки подчиняются двум правилам, и нарушение любого ломает React. Первое: вызывать хуки только на верхнем уровне функции - не внутри условий, циклов или вложенных функций. Второе: вызывать хуки только из React-компонентов или из других хуков, а не из обычных функций. Правила не каприз: React сопоставляет состояние с хуком по порядку его вызова между рендерами.

React не знает имён переменных - он помнит хуки по их позиции. Первый вызов useState на этом рендере получает первую ячейку, второй - вторую, и так далее. Если обернуть вызов в if, порядок поедет между рендерами: на одном рендере хук вызвался, на другом нет, и React сопоставит состояние не с тем хуком. Отсюда жёсткое требование к стабильному порядку вызовов.

Ранний return до вызова хуков - тоже нарушение: если функция иногда выходит раньше, чем дошла до useState, на разных рендерах вызовется разное число хуков. Сначала все хуки, потом любые условные return. Условную логику размещают внутри тела хука или после всех его вызовов, но не вокруг них.

Внутреннее устройство объясняет строгость: React держит для компонента упорядоченный список ячеек состояния и на каждом рендере проходит его по порядку вызовов хуков. Стабильный порядок - единственный способ для React сопоставить ячейку с нужным хуком. Поэтому ветвление вокруг вызова хука недопустимо в принципе.

Почему нельзя вызывать хук внутри условия if?

Хуки делятся логикой, а не состоянием

Распространённое заблуждение: если два компонента вызывают один и тот же свой хук, они делят и состояние. Это не так. Свой хук переиспользует логику, но каждый его вызов создаёт свой независимый экземпляр состояния. Если три компонента вызвали useOnlineStatus, у каждого свой useState и свой эффект внутри. Они не связаны: общая только реализация, но не данные.

  • Делится логика — Код подписки, очистки и обновления записан один раз в хуке. Любой компонент переиспользует этот код вызовом хука. Правка хука чинит баг сразу везде
  • Не делится состояние — Каждый вызов хука заводит собственные useState и useEffect. Значение online в одном компоненте никак не связано с online в другом. Чтобы делить данные, нужны контекст или стор

Если состояние действительно должно быть общим для нескольких компонентов - один список задач, одна корзина - его поднимают наверх в общий компонент и раздают через props или контекст, либо кладут в стор вроде Zustand. Свой хук для этого не предназначен: он копирует логику, а не объединяет данные. Понимание этой границы убирает целый класс ошибок 'почему мои компоненты не видят изменений друг друга'.

Плагин eslint-plugin-react-hooks автоматизирует контроль. Правило rules-of-hooks ловит вызовы под условиями и в неположенных местах, а exhaustive-deps подсказывает пропущенные зависимости эффектов. В современных стартерах он включён по умолчанию, и игнорировать его предупреждения почти всегда ошибка.

Два компонента вызывают один и тот же свой хук useCounter. Что у них общего?

Связь с другими темами

Урок про переиспользование логики. Опирается на:

  • useEffect — Самый частый кандидат на вынос в свой хук - связка состояния и эффекта подписки
  • Жизненный цикл эффекта — useEffectEvent обычно объявляют внутри своего хука рядом с его эффектом
  • Модель рендера — Правила хуков следуют из того, что React опознаёт хуки по порядку их вызова

Итог

  • Свой хук - это функция с именем на use, которая вызывает другие хуки и инкапсулирует логику с состоянием для переиспользования
  • Правила хуков: вызывать только на верхнем уровне (не в условиях, циклах, вложенных функциях) и только из компонентов или других хуков
  • Свои хуки делятся логикой, а не состоянием: каждый компонент, вызвавший хук, получает свой собственный изолированный экземпляр состояния
  • Имя обязано начинаться с use - по нему React и линтер понимают, что внутри могут вызываться хуки, и применяют правила
  • eslint-plugin-react-hooks автоматически проверяет соблюдение правил хуков и полноту массивов зависимостей

Связанные уроки

  • rc-11-useeffect — Чаще всего в свой хук выносят именно связку state + эффект, поэтому без useEffect эта тема преждевременна
  • rc-12-effect-lifecycle — Внутри своего хука обычно живёт useEffectEvent рядом с эффектом подписки
  • rc-10-render-mental-model — Правила хуков напрямую вытекают из того, как React сопоставляет вызовы хуков по порядку между рендерами
Свои хуки и правила хуков

0

1

Войти