0Pricing
Frontend Academy · Урок

Пользовательские хуки: выделение повторно используемой логики

Переносите логику с состоянием в пользовательские функции use*, чтобы делиться поведением между компонентами без дублирования кода и подъёма состояния.

«Пользовательские хуки: выделение повторно используемой логики» — бесплатный урок Frontend Academy на CoddyKit. Это урок 4 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения Frontend Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс Frontend Academy содержит 4 уроков всего.

Что такое пользовательские хуки?

Пользовательский хук — это функция JavaScript, имя которой начинается с use и которая внутри вызывает другие хуки. Пользовательские хуки позволяют извлекать логику работы с состоянием, чтобы несколько компонентов могли совместно использовать её без копирования.

Извлечение логики в пользовательский хук

Найдите логику, дублирующуюся в компонентах (загрузка данных + состояние загрузки + ошибка, размер окна, локальное хранилище), и перенесите её в пользовательский хук.

function useWindowWidth() {
  const [width, setWidth] = useState(window.innerWidth);

  useEffect(() => {
    const handler = () => setWidth(window.innerWidth);
    window.addEventListener('resize', handler);
    return () => window.removeEventListener('resize', handler);
  }, []);

  return width;
}

// Usage:
const width = useWindowWidth();
const isMobile = width < 768;

useFetch — хук для загрузки данных

Повторно используемый хук для загрузки данных, с помощью которого любой компонент может загружать данные по URL.

function useFetch<T>(url: string) {
  const [data, setData] = useState<T | null>(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<string | null>(null);

  useEffect(() => {
    const controller = new AbortController();
    async function load() {
      try {
        const res = await fetch(url, { signal: controller.signal });
        if (!res.ok) throw new Error(`${res.status}`);
        setData(await res.json());
      } catch (err) {
        if ((err as DOMException).name !== 'AbortError')
          setError((err as Error).message);
      } finally { setLoading(false); }
    }
    load();
    return () => controller.abort();
  }, [url]);

  return { data, loading, error };
}

Хук useLocalStorage

Пользовательский хук, который синхронизирует значение с localStorage и поддерживает синхронизацию между вкладками через событие storage.

function useLocalStorage<T>(key: string, initialValue: T) {
  const [value, setValue] = useState<T>(() => {
    const stored = localStorage.getItem(key);
    return stored ? JSON.parse(stored) : initialValue;
  });

  const set = useCallback((newValue: T) => {
    setValue(newValue);
    localStorage.setItem(key, JSON.stringify(newValue));
  }, [key]);

  return [value, set] as const;
}

Пользовательские хуки совместно используют логику, а не состояние

Каждый компонент, вызывающий пользовательский хук, получает собственное независимое состояние. Хуки не являются одиночками — это вызовы функций, создающие изолированные экземпляры состояния.

Соглашения об именовании

Имена пользовательских хуков должны начинаться с use. Это проверяется правилами ESLint для react-hooks, которые гарантируют, что хуки вызываются на верхнем уровне и в правильном порядке.

Возврат нескольких значений

Пользовательские хуки обычно возвращают объект (именованные значения) или кортеж (как useState). Кортежи позволяют выполнять деструктурирование с пользовательскими именами, а объекты сами объясняют своё содержимое.

// Tuple (like useState):
const [value, setValue] = useLocalStorage('key', 0);

// Object (more descriptive):
const { data, loading, error, refetch } = useFetch('/api/users');

Хук useDebounce

Применяйте дебаунс к значению: обновляйте его только после того, как пользователь перестанет печатать в течение заданной задержки.

function useDebounce<T>(value: T, delay: number): T {
  const [debounced, setDebounced] = useState(value);

  useEffect(() => {
    const timer = setTimeout(() => setDebounced(value), delay);
    return () => clearTimeout(timer);
  }, [value, delay]);

  return debounced;
}

// Usage:
const debouncedSearch = useDebounce(searchTerm, 300);
useEffect(() => { search(debouncedSearch); }, [debouncedSearch]);

Хук useMediaQuery

Оборачивает window.matchMedia в хук, чтобы реактивно получать результаты CSS-медиазапросов.

function useMediaQuery(query: string): boolean {
  const [matches, setMatches] = useState(() => window.matchMedia(query).matches);

  useEffect(() => {
    const mq = window.matchMedia(query);
    const handler = (e: MediaQueryListEvent) => setMatches(e.matches);
    mq.addEventListener('change', handler);
    return () => mq.removeEventListener('change', handler);
  }, [query]);

  return matches;
}

Тестирование пользовательских хуков

Используйте утилиту @testing-library/react renderHook(), чтобы тестировать пользовательские хуки отдельно, не оборачивая их в компонент.

import { renderHook, act } from '@testing-library/react';

test('useCounter increments', () => {
  const { result } = renderHook(() => useCounter(0));
  expect(result.current.count).toBe(0);
  act(() => result.current.increment());
  expect(result.current.count).toBe(1);
});

Опубликованные библиотеки хуков

Библиотеки хуков от сообщества предоставляют сотни проверенных хуков: @uidotdev/usehooks, react-use, usehooks-ts. Прежде чем писать пользовательский хук, проверьте, существует ли уже хорошо протестированная версия.

Быстрая проверка

Что происходит с состоянием внутри пользовательского хука, когда его вызывают два разных компонента?

Краткое повторение: пользовательские хуки

Пользовательские хуки извлекают логику работы с состоянием для повторного использования. Используйте префикс use в имени. Каждый компонент, вызывающий хук, получает независимое состояние. Возвращайте объекты или кортежи. Распространённые шаблоны: useFetch, useLocalStorage, useDebounce, useMediaQuery. Тестируйте с помощью renderHook(). Перед написанием с нуля проверяйте библиотеки от сообщества.

Часто задаваемые вопросы

Урок «Пользовательские хуки: выделение повторно используемой логики» бесплатный?

Да — полный текст урока «Пользовательские хуки: выделение повторно используемой логики» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс Frontend Academy, подпишись на CoddyKit PRO. Курс Frontend Academy содержит 4 уроков всего.

Чему я научусь в уроке «Пользовательские хуки: выделение повторно используемой логики»?

Переносите логику с состоянием в пользовательские функции use*, чтобы делиться поведением между компонентами без дублирования кода и подъёма состояния. Ты практикуешь Frontend Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.

Нужен ли мне опыт, чтобы начать Frontend Academy?

Предыдущий опыт не требуется. Frontend Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 4 из 4.

Сколько времени занимает урок «Пользовательские хуки: выделение повторно используемой логики»?

Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.

Можно ли писать и запускать код в этом уроке Frontend Academy?

Да. Каждый урок Frontend Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.

Все уроки этого курса

  1. useContext для глобального состояния
  2. useReducer для сложного состояния
  3. useMemo и useCallback для производительности
  4. Пользовательские хуки: выделение повторно используемой логики
← Назад к Frontend Academy