Асинхронность и React

В React асинхронные операции, такие как загрузка данных с сервера, должны выполняться в правильном месте жизненного цикла компонента. Неправильное использование асинхронности может привести к ошибкам, утечкам памяти или обновлению состояния в уже размонтированном компоненте.

Где выполнять асинхронные операции

В функциональных компонентах асинхронный код запускается внутри хука useEffect. Это гарантирует, что операция выполнится в нужный момент — например, при монтировании компонента или при изменении зависимостей.

import { useEffect, useState } from 'react';

function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    const fetchUser = async () => {
      try {
        const response = await fetch(`/api/users/${userId}`);
        const data = await response.json();
        setUser(data);
      } catch (error) {
        console.error('Ошибка загрузки пользователя:', error);
      } finally {
        setLoading(false);
      }
    };

    fetchUser();
  }, [userId]); // Зависимость: перезапускать при смене userId

  if (loading) return <div>Загрузка...</div>;
  return <div>Имя: {user?.name}</div>;
}

Почему нельзя использовать await напрямую в компоненте

Следующий код недопустим:

// Ошибка: нельзя использовать await вне async-функции
const data = await fetch('/api/data');

Компонент React — это обычная функция, и она должна синхронно возвращать JSX. Асинхронная логика должна быть изолирована внутри useEffect или вспомогательных функций.

Обработка размонтирования

Если компонент размонтируется до завершения запроса, попытка обновить его состояние вызовет предупреждение. Чтобы этого избежать, можно использовать флаг:

useEffect(() => {
  let isMounted = true;

  const fetchUser = async () => {
    const data = await fetch(`/api/users/${userId}`).then(r => r.json());
    if (isMounted) {
      setUser(data);
    }
  };

  fetchUser();

  return () => {
    isMounted = false; // cleanup при размонтировании
  };
}, [userId]);

В большинстве случаев React Native подавляет такие ошибки, но хорошая практика — предотвращать их явно.

Работа с состоянием

При асинхронных операциях рекомендуется управлять тремя состояниями:

  • loading — идёт загрузка
  • data — успешный результат
  • error — произошла ошибка

Это улучшает UX и помогает избежать неопределённого поведения.

Важно помнить

  • Все побочные эффекты (запросы, подписки, таймеры) — только в useEffect
  • Асинхронная функция внутри useEffect должна быть объявлена отдельно или как немедленно вызываемая стрелочная функция
  • Не забывайте указывать зависимости в массиве useEffect, чтобы избежать устаревших замыканий
  • Избегайте запуска асинхронных операций в обработчиках рендера (например, прямо в теле компонента)

Асинхронность в React требует дисциплины, но при правильном подходе делает приложения отзывчивыми и надёжными.