> Насколько хорошей практикой является вызов fetch внутри useEffect в React (React)

Уровень: senior · Роль: frontend · Категория: Технические вопросы

Компании: циан

Стек: React

> Пример ответа

Короткий ответ

Вызов fetch внутри useEffect - допустимая, но не всегда оптимальная практика для React-приложений. Она работает для простых случаев, но приводит к проблемам с race conditions, отсутствием кеширования, повторными запросами при ререндерах и сложностью с отменой запросов. Для продакшена лучше использовать специализированные решения вроде React Query, SWR или RTK Query, которые предоставляют встроенное управление состоянием загрузки, кеширование и дедупликацию запросов.

Подробное объяснение

Проблема не в самом useEffect как таковом, а в том, что нативный fetch не интегрирован с жизненным циклом React-компонента. Основные недостатки:

  • Race conditions: при быстром изменении зависимостей ответ от предыдущего запроса может перезаписать актуальные данные
  • Отсутствие отмены запросов: fetch не отменяется автоматически при размонтировании компонента, что приводит к memory leaks и ошибкам при обновлении размонтированного компонента
  • Повторные запросы: useEffect срабатывает при каждом изменении зависимостей, даже если данные уже были получены
  • Ручное управление состоянием: нужно вручную обрабатывать loading, error и success состояния
  • Отсутствие кеширования: каждый размонтированный и повторно смонтированный компонент делает новый запрос

Хорошая практика - использовать useEffect с fetch только для:

  • Прототипов и небольших pet-проектов
  • Единичных запросов без сложной логики
  • Образовательных целей

Для production-приложений предпочтительнее библиотеки, которые решают эти проблемы: React Query, SWR, RTK Query, Apollo Client.

На практике

При использовании fetch в useEffect необходимо:

  1. Добавить cleanup-функцию с AbortController для отмены запросов
  2. Обрабатывать race conditions через флаг mounted или отмену
  3. Реализовать ручное кеширование через useState/useRef
  4. Управлять состояниями загрузки и ошибок вручную

Альтернативы:

  • React Query (TanStack Query): автоматическое кеширование, дедупликация, stale-while-revalidate, рефетч при фокусе окна
  • SWR: легковесный, стратегия stale-while-revalidate, встроенная поддержка revalidation
  • RTK Query: интеграция с Redux Toolkit, автоматическое управление кешем и состояниями
  • useReducer + fetch: для сложной логики без внешних зависимостей

Пример кода

Плохая практика (без обработки race conditions и отмены):

JAVASCRIPT
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
useEffect(() => {
fetch(`/api/users/${userId}`)
.then(res => res.json())
.then(data => setUser(data));
}, [userId]);
return <div>{user?.name}</div>;
}

Хорошая практика (с отменой и защитой от race conditions):

JAVASCRIPT
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const abortController = new AbortController();
let cancelled = false;
setLoading(true);
setError(null);
fetch(`/api/users/${userId}`, { signal: abortController.signal })
.then(res => {
if (!res.ok) throw new Error('Network error');
return res.json();
})
.then(data => {
if (!cancelled) {
setUser(data);
setLoading(false);
}
})
.catch(err => {
if (!cancelled && err.name !== 'AbortError') {
setError(err.message);
setLoading(false);
}
});
return () => {
cancelled = true;
abortController.abort();
};
}, [userId]);
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error}</div>;
return <div>{user?.name}</div>;
}

Лучшая практика (с React Query):

JAVASCRIPT
import { useQuery } from '@tanstack/react-query';
function UserProfile({ userId }) {
const { data: user, isLoading, error } = useQuery({
queryKey: ['user', userId],
queryFn: () => fetch(`/api/users/${userId}`).then(res => res.json()),
});
if (isLoading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
return <div>{user?.name}</div>;
}

Как отвечать на собеседовании

Начни с признания, что это распространённый паттерн, но не лучшая практика для продакшена. Объясни основные проблемы: race conditions, отсутствие отмены, ручное управление состоянием. Покажи, как улучшить код с помощью AbortController и флага cancelled. Затем предложи более современные решения - React Query или SWR. Подчеркни, что выбор зависит от контекста: для простых страниц с одним запросом useEffect может быть ок, для сложного UI с множеством запросов лучше использовать специализированные библиотеки.

Что проверяет интервьюер

  • Понимание жизненного цикла React-компонентов и эффектов
  • Знание проблем асинхронности в React (race conditions, memory leaks)
  • Умение писать чистый и безопасный код с обработкой ошибок
  • Осведомлённость о современных подходах к управлению данными
  • Способность выбирать правильный инструмент под задачу

Типичные ошибки

  • Отсутствие cleanup-функции в useEffect
  • Игнорирование race conditions при быстрых изменениях зависимостей
  • Необработка ошибок сети и HTTP-статусов
  • Отсутствие отмены запроса при размонтировании компонента
  • Использование async/await без try/catch в useEffect
  • Утверждение, что fetch в useEffect - всегда плохая практика (без учёта контекста)

> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?

Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью