Пользовательские хуки: выделение повторно используемой логики
Переносите логику с состоянием в пользовательские функции 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 — локальная установка не требуется.
Все уроки этого курса
- useContext для глобального состояния
- useReducer для сложного состояния
- useMemo и useCallback для производительности
- Пользовательские хуки: выделение повторно используемой логики