Когда нужен кастомный hook?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Когда нужен кастомный хук?
Кастомный хук (custom hook) в React — это механизм, позволяющий извлекать и повторно использовать логику состояния и побочные эффекты из компонентов, сохраняя при этом принцип чистых функций и декларативного описания UI. Его создание оправдано в нескольких ключевых сценариях, которые я, как опытный разработчик, разделю на категории.
Основные критерии для создания кастомного хука
1. Повторное использование логики в разных компонентах
Это основная причина. Если вы обнаруживаете, что один и тот же код, использующий useState, useEffect, useContext или другие хуки, повторяется в нескольких компонентах — это прямой сигнал для создания кастомного хука.
// До: дублирование в компонентах UserList и Profile
function UserList() {
const [users, setUsers] = useState([]);
const [loading, setLoading] = useState(false);
useEffect(() => {
setLoading(true);
fetch('/api/users')
.then(res => res.json())
.then(data => setUsers(data))
.finally(() => setLoading(false));
}, []);
return /* ... */;
}
// После: хук useFetch
function useFetch(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
setLoading(true);
fetch(url)
.then(res => res.json())
.then(setData)
.finally(() => setLoading(false));
}, [url]);
return { data, loading };
}
// Использование в любом компоненте
function UserList() {
const { data: users, loading } = useFetch('/api/users');
return /* ... */;
}
2. Изоляция сложной логики для читаемости
Когда компонент становится слишком большим и сложным из-за множества состояний и эффектов, кастомный хук помогает разделить ответственность. Компонент остается чистым описанием UI, а вся бизнес-логика уходит в хуки.
// Сложный компонент с обработкой формы, валидацией и отправкой
function RegistrationForm() {
// 5-7 состояний, 3-4 эффекта, несколько обработчиков
// Трудно читать и поддерживать
}
// Решение: выделить логику в хук useForm
function useForm(initialValues, validationRules) {
const [values, setValues] = useState(initialValues);
const [errors, setErrors] = useState({});
const [isSubmitting, setIsSubmitting] = useState(false);
const handleChange = (e) => {
setValues({ ...values, [e.target.name]: e.target.value });
// Валидация по правилам validationRules
};
const handleSubmit = async (callback) => {
setIsSubmitting(true);
// Асинхронная обработка и валидация
await callback(values);
setIsSubmitting(false);
};
return { values, errors, isSubmitting, handleChange, handleSubmit };
}
// Теперь компонент чистый
function RegistrationForm() {
const form = useForm({ email: '', password: '' }, validationRules);
return (
<form onSubmit={() => form.handleSubmit(submitApiCall)}>
{/* Поля связаны с form.values и form.handleChange */}
</form>
);
}
3. Абстракция над сторонними библиотеками или API
Если вы работаете с сложными внешними библиотеками (например, картами, графиками, WebSocket), кастомный хук создает удобный интерфейс взаимодействия, скрывая детали реализации.
// Хук для WebSocket соединения
function useWebSocket(url) {
const [messages, setMessages] = useState([]);
const [connection, setConnection] = useState(null);
useEffect(() => {
const ws = new WebSocket(url);
ws.onmessage = (event) => {
setMessages(prev => [...prev, JSON.parse(event.data)]);
};
setConnection(ws);
return () => ws.close();
}, [url]);
const sendMessage = (data) => {
if (connection && connection.readyState === WebSocket.OPEN) {
connection.send(JSON.stringify(data));
}
};
return { messages, sendMessage };
}
4. Создание композиции из существующих хуков
Часто кастомный хук не содержит новой логики, а просто комбинирует стандартные хуки для решения типовой задачи. Например, управление модальными окнами или синхронизация с localStorage.
// Хук для синхронизации состояния с localStorage
function useLocalStorage(key, initialValue) {
const [storedValue, setStoredValue] = useState(() => {
try {
const item = window.localStorage.getItem(key);
return item ? JSON.parse(item) : initialValue;
} catch (error) {
console.error(error);
return initialValue;
}
});
const setValue = (value) => {
try {
setStoredValue(value);
window.localStorage.setItem(key, JSON.stringify(value));
} catch (error) {
console.error(error);
}
};
return [storedValue, setValue];
}
Практические примеры из реальных проектов
В моей практике кастомные хуки часто создаются для:
- Управление состоянием форм (валидация, сабмит, сброс) — хук
useForm. - Запросы к API с обработкой загрузки, ошибок и кэширования — хуки
useFetch,useQuery. - Интернационализация — хук
useTranslationдля работы с i18n. - Аутентификация и доступ к контексту пользователя —
useAuth. - Работа с таймерами и дебаунсингом —
useDebounce,useInterval. - Интерактивность с браузерным API —
useMediaQuery,useGeolocation,useOnlineStatus.
Принципы хорошего кастомного хука
- Имя начинается с
use— это конвенция React для идентификации хуков. - Один хук — одна ответственность — не создавайте "монстров", объединяющих несвязанные задачи.
- Чистые функции — хук должен возвращать стабильный интерфейс (объект, массив, функцию) и не вызывать непредсказуемых побочных эффектов вне
useEffect. - Документация через код — четкие параметры и возвращаемые значения.
Заключение
Кастомный хук нужен когда логика выходит за рамки описания UI и требует повторного использования или абстракции. Он превращает React из библиотеки для создания компонентов в мощный инструмент для построения архитектуры приложения, где бизнес-логика четко отделена от представления. Это следующий уровень после освоения базовых хуков — переход от "как сделать" к "как правильно организовать". В современных приложениях с использованием TypeScript кастомные хуки также становятся ключевым местом для описания типов данных и контрактов между частями системы.