> Как правильно использовать useState и useEffect в кастомном хуке для хранения данных и ошибок (React)
Уровень: senior · Роль: frontend · Категория: Технические вопросы
Компании: Консорциум кодекс
Стек: React
> Пример ответа
Короткий ответ
Для хранения данных и ошибок в кастомном хуке используйте два отдельных useState: const [data, setData] = useState(null) и const [error, setError] = useState(null). useEffect выполняет асинхронный запрос, обновляя state через setData при успехе или setError при ошибке. Обязательно добавляйте cleanup-функцию для отмены запроса при размонтировании, чтобы избежать утечек памяти и race conditions.
Подробное объяснение
Кастомный хук для работы с данными должен учитывать жизненный цикл компонента и возможные состояния: загрузка, успех, ошибка. Использование отдельных useState для данных и ошибок даёт чёткое разделение ответственности и упрощает типизацию.
Ключевые моменты:
- Разделение state: data и error хранятся отдельно, что позволяет компоненту гибко обрабатывать каждое состояние
- Управление загрузкой: добавьте третий state
loadingдля индикации процесса - Cleanup в useEffect: возвращайте функцию, которая отменяет запрос (через AbortController или флаг), чтобы предотвратить обновление state после размонтирования
- Зависимости: передавайте в useEffect массив зависимостей, чтобы перезапрашивать данные при изменении параметров
- Обработка ошибок: используйте try/catch внутри асинхронной функции, устанавливая error при любом исключении
Такой подход соответствует принципам React и позволяет переиспользовать логику запросов без дублирования кода.
На практике
При реализации хука следуйте этим правилам:
- Всегда инициализируйте state с явным типом:
useState<Data | null>(null) - Используйте AbortController для отмены запросов:
const controller = new AbortController() - Не вызывайте setState после размонтирования - проверяйте флаг
isMountedили используйте AbortController - Для зависимостей передавайте только примитивы или стабильные ссылки (через useMemo/useCallback)
- Возвращайте из хука объект с полями data, error, loading и, опционально, refetch для повторного запроса
Пример кода
TYPESCRIPTimport { useState, useEffect, useCallback } from 'react';interface UseFetchResult<T> {data: T | null;error: Error | null;loading: boolean;refetch: () => void;}function useFetch<T>(url: string): UseFetchResult<T> {const [data, setData] = useState<T | null>(null);const [error, setError] = useState<Error | null>(null);const [loading, setLoading] = useState<boolean>(true);const fetchData = useCallback(async (signal: AbortSignal) => {setLoading(true);setError(null);try {const response = await fetch(url, { signal });if (!response.ok) {throw new Error(`HTTP error! status: ${response.status}`);}const result = await response.json();if (!signal.aborted) {setData(result);}} catch (err) {if (!signal.aborted) {setError(err instanceof Error ? err : new Error('Unknown error'));}} finally {if (!signal.aborted) {setLoading(false);}}}, [url]);useEffect(() => {const controller = new AbortController();fetchData(controller.signal);return () => controller.abort();}, [fetchData]);const refetch = useCallback(() => {const controller = new AbortController();fetchData(controller.signal);return () => controller.abort();}, [fetchData]);return { data, error, loading, refetch };}
Как отвечать на собеседовании
Начните с краткого описания задачи - хук для работы с асинхронными данными. Подчеркните важность управления жизненным циклом: cleanup предотвращает обновление state после размонтирования. Упомяните типизацию через generics и разделение состояний (data, error, loading). Приведите пример с AbortController как современный подход. Если спросят про альтернативы, скажите о React Query/SWR для production, но отметьте, что кастомный хук даёт полный контроль. Покажите понимание edge cases: повторные запросы при изменении url, обработка ошибок сети, корректная типизация.
Что проверяет интервьюер
- Понимание lifecycle React-компонентов и эффектов
- Умение работать с асинхронностью в React (race conditions, cleanup)
- Знание TypeScript и типизации хуков
- Способность проектировать переиспользуемые абстракции
- Понимание trade-off между кастомным хуком и готовыми решениями
- Внимание к деталям: обработка ошибок, индикация загрузки, refetch
Типичные ошибки
- Игнорирование cleanup: setState после размонтирования вызывает warning и утечки
- Отсутствие обработки ошибок: не обёрнутый в try/catch fetch приведёт к необработанному rejection
- Неправильные зависимости: пропуск url в массиве зависимостей или включение нестабильных ссылок
- Смешивание состояний: хранение data и error в одном объекте усложняет типизацию и обработку
- Забывают про loading: без него компонент не может показать спиннер или скелетон
- Использование async функции напрямую в useEffect без обёртки: это приводит к ошибке, так как useEffect ожидает синхронную функцию или cleanup
> Похожие задачи по frontend
Что такое HOC (Higher Order Component) в React
Что такое мемоизация
Как правильно обрабатывать асинхронные функции в useEffect и почему нельзя делать колбэк асинхронным
Зачем из useEffect возвращать callback и когда он вызывается
> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?
Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью