Что такое custom hooks в React?
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
- Имя обязательно начинается с
use— иначе линтер и React не смогут проверить соблюдение правил хуков - Хуки можно вызывать только на верхнем уровне функции, не внутри условий или циклов
- 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, нарушая правила хуков


