Что такое custom hooks в React?

MiddleReact · Frontend·Обновлено 9 июля 2026
Коротко
Custom hooks — это функции на JavaScript, имя которых начинается с use, которые позволяют выносить повторяющуюся логику с использованием встроенных хуков React в переиспользуемые единицы. Они не добавляют новых возможностей React, но позволяют организовывать и переиспользовать stateful-логику между компонентами.

Что такое custom hooks

Custom hooks — это обычные JavaScript-функции, которые:

  • Начинаются с префикса use (соглашение, которое позволяет React применять правила хуков)
  • Могут вызывать другие хуки React внутри себя (useState, useEffect, useRef и т.д.)
  • Возвращают любые данные, которые нужны компоненту

Главная цель — выделить логику из компонента, чтобы её можно было переиспользовать в нескольких местах без дублирования кода.

Зачем нужны custom hooks

До появления хуков логику приходилось переиспользовать через HOC (Higher-Order Components) или render props — оба паттерна усложняли дерево компонентов и ухудшали читаемость. Custom hooks решают ту же задачу без обёрток, сохраняя логику отдельно от представления.

Типичные сценарии использования:

  • Работа с формами (валидация, отслеживание значений)
  • Загрузка данных из API
  • Подписка на события браузера (resize, scroll)
  • Работа с localStorage / sessionStorage
  • Дебаунс и тротлинг значений

Правила написания custom hooks

  1. Имя обязательно начинается с use — иначе линтер и React не смогут проверить соблюдение правил хуков
  2. Хуки можно вызывать только на верхнем уровне функции, не внутри условий или циклов
  3. Custom hook — это не компонент: он не возвращает JSX

Пример: хук для работы с localStorage

import { useState } from 'react';

function useLocalStorage<T>(key: string, initialValue: T) {
  const [storedValue, setStoredValue] = useState<T>(() => {
    try {
      const item = window.localStorage.getItem(key);
      // Парсим сохранённое значение или возвращаем начальное
      return item ? JSON.parse(item) : initialValue;
    } catch {
      return initialValue;
    }
  });

  const setValue = (value: T) => {
    try {
      setStoredValue(value);
      window.localStorage.setItem(key, JSON.stringify(value));
    } catch (error) {
      console.error(error);
    }
  };

  return [storedValue, setValue] as const;
}

Важный нюанс: изоляция состояния

Каждый компонент, использующий один и тот же custom hook, получает собственный изолированный экземпляр состояния. Custom hooks не «шарят» state между компонентами — они лишь переиспользуют логику. Чтобы разделить состояние между компонентами, нужно поднять его выше или использовать контекст.

Отличие от утилитарной функции

Обычная утилитарная функция не может вызывать хуки React. Custom hook — может. Если логика требует useState, useEffect или других хуков — нужен custom hook. Если достаточно чистой функции без реактивного состояния — используйте обычную функцию.

Что хочет услышать интервьюер

Кандидат объяснит, что custom hook — это функция с префиксом use, которая может вызывать другие хуки

Кандидат назовёт главную цель — переиспользование stateful-логики между компонентами

Кандидат понимает, что у каждого компонента — своя изолированная копия состояния хука

Кандидат приведёт практический пример: useFetch, useForm, useDebounce или аналогичный

Кандидат объяснит разницу между custom hook и обычной вспомогательной функцией

Пример: useDebounce — классический пример custom hook

import { useState, useEffect } from 'react';

// Задерживает обновление значения на указанное время
function useDebounce<T>(value: T, delay: number): T {
  const [debouncedValue, setDebouncedValue] = useState<T>(value);

  useEffect(() => {
    const timer = setTimeout(() => {
      setDebouncedValue(value);
    }, delay);

    // Сбрасываем таймер при каждом изменении value
    return () => clearTimeout(timer);
  }, [value, delay]);

  return debouncedValue;
}

// Использование в компоненте
function SearchInput() {
  const [query, setQuery] = useState('');
  const debouncedQuery = useDebounce(query, 500);

  useEffect(() => {
    if (debouncedQuery) {
      // Запрос отправится только через 500ms после остановки ввода
      console.log('Поиск по запросу:', debouncedQuery);
    }
  }, [debouncedQuery]);

  return (
    <input
      value={query}
      onChange={(e) => setQuery(e.target.value)}
      placeholder="Введите запрос..."
    />
  );
}

Пример: useFetch — загрузка данных с обработкой состояний

import { useState, useEffect } from 'react';

interface FetchState<T> {
  data: T | null;
  loading: boolean;
  error: string | null;
}

function useFetch<T>(url: string): FetchState<T> {
  const [state, setState] = useState<FetchState<T>>({
    data: null,
    loading: true,
    error: null,
  });

  useEffect(() => {
    let cancelled = false; // Защита от race condition

    setState({ data: null, loading: true, error: null });

    fetch(url)
      .then((res) => res.json())
      .then((data) => {
        if (!cancelled) {
          setState({ data, loading: false, error: null });
        }
      })
      .catch((err) => {
        if (!cancelled) {
          setState({ data: null, loading: false, error: err.message });
        }
      });

    return () => {
      cancelled = true; // Отменяем обновление при размонтировании
    };
  }, [url]);

  return state;
}

// Использование
function UserProfile({ userId }: { userId: number }) {
  const { data, loading, error } = useFetch<{ name: string }>(
    `/api/users/${userId}`
  );

  if (loading) return <p>Загрузка...</p>;
  if (error) return <p>Ошибка: {error}</p>;
  return <p>Пользователь: {data?.name}</p>;
}

Типичные ошибки

Путают custom hooks с обычными утилитарными функциями — не понимают, зачем нужен префикс use

Думают, что два компонента, использующие один хук, автоматически разделяют состояние

Называют хук без префикса use, из-за чего React не может применить правила хуков

Возвращают JSX из custom hook, фактически превращая его в компонент

Вызывают хуки внутри условий или циклов внутри custom hook, нарушая правила хуков

Лучшие курсы по теме

изображение курса

TypeScript с нуля

Антон Ларичев
AI-тренажерыAI-тренажеры
Практика в студииПрактика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.8
3 999 ₽ 6 990 ₽
Подробнее
изображение курса

Feature-Sliced Design

Антон Ларичев
AI-тренажерыAI-тренажеры
Практика в студииПрактика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.5
3 999 ₽ 6 990 ₽
Подробнее
изображение курса

Next.js - с нуля

Антон Ларичев
AI-тренажерыAI-тренажеры
Практика в студииПрактика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.7
3 999 ₽ 6 990 ₽
Подробнее