Кастомные хуки вглубь

Урок про кастомные хуки вглубь: как выносить логику из компонентов и компоновать хуки друг из друга, не нарушая правил хуков.

Кастомный хук — это функция с именем на use…, которая вызывает другие хуки и инкапсулирует кусок логики с состоянием, переиспользуемый между компонентами.

Что кастомный хук на самом деле делает

Кастомный хук — не магия и не «общее состояние». Это просто способ вынести вызовы useState/useEffect/useMemo в отдельную функцию. Каждый компонент, вызвавший хук, получает свой собственный, независимый экземпляр этого состояния. Хук переиспользует логику, а не данные.

function useToggle(initial = false) {
  const [on, setOn] = React.useState(initial);
  const toggle = React.useCallback(() => setOn((v) => !v), []);
  return [on, toggle];
}

// использование — у каждого свой независимый стейт
function Panel() {
  const [open, toggleOpen] = useToggle();
  return <button onClick={toggleOpen}>{open ? "Скрыть" : "Показать"}</button>;
}

Зачем выделять

  • Переиспользование логики без копипаста: подписки, дебаунс, загрузка данных, формы.
  • Читаемость компонента: тело компонента становится «что показать», а не «как посчитать».
  • Тестируемость: логику хука проще тестировать отдельно от вёрстки.

Композиция хуков

Хуки складываются как функции. Кастомный хук может вызывать другие кастомные хуки. Соберём useDebouncedValue и используем его внутри useSearch.

function useDebouncedValue(value, delay) {
  const [debounced, setDebounced] = React.useState(value);
  React.useEffect(() => {
    const id = setTimeout(() => setDebounced(value), delay);
    return () => clearTimeout(id);
  }, [value, delay]);
  return debounced;
}

function useSearch(query) {
  const debounced = useDebouncedValue(query, 300);
  const [results, setResults] = React.useState([]);
  React.useEffect(() => {
    if (!debounced) return;
    fetchResults(debounced).then(setResults);
  }, [debounced]);
  return results;
}

Так строится «лесенка» абстракций: низкоуровневый хук решает одну задачу (дебаунс), верхний — компонует его с загрузкой. Компонент остаётся чистым.

Правила, которые нельзя нарушать

  1. Вызывайте хуки только на верхнем уровне функции — не в условиях, циклах или вложенных функциях. React опирается на порядок вызовов между рендерами.
  2. Вызывайте хуки только из React-компонентов или из других хуков. Имя обязано начинаться с use — по нему линтер проверяет правила.
  3. Указывайте все внешние значения в массиве зависимостей useEffect/useMemo/useCallback — иначе получите «залипшее» (stale) значение.

Когда НЕ стоит делать хук

Если логика используется в одном месте и не содержит состояния/эффектов — это просто обычная функция, а не хук. Не превращайте чистую функцию formatPrice(n) в useFormatPrice — лишний use только обманет линтер и читателя.

Итог

  • Кастомный хук переиспользует логику; состояние у каждого вызова своё.
  • Хуки компонуются: одни кастомные хуки строятся из других.
  • Соблюдайте правила хуков: верхний уровень, имя на use, полные зависимости.
  • Без состояния и эффектов — делайте обычную функцию, а не хук.
Проверьте себя
1. Что переиспользует кастомный хук между компонентами?
AОбщее состояние на всех
BЛогику; состояние у каждого вызова своё
CТолько разметку JSX
DГлобальные переменные
2. Можно ли вызвать кастомный хук внутри условия `if`?
AДа, если он возвращает массив
BНет, хуки вызывают только на верхнем уровне
CДа, в любом месте функции
DТолько внутри useEffect
3. Когда логику НЕ стоит оформлять как хук?
AКогда она использует useEffect
BКогда это чистая функция без состояния и эффектов
CКогда она нужна в двух компонентах
DКогда она вызывает другой хук
Поддержать проект