Программирование с ИИ
ENRU
10.4

Эффекты

где React ломает новичков чаще всего

Что это

useEffect — способ сделать что-то, что не является отрисовкой: запросить данные, подписаться на событие, поставить таймер. Выполняется после того, как компонент нарисовался.

Это самое сложное место React и самое частое место ошибок ИИ. Одна забытая строка превращает приложение в бесконечный цикл запросов.

Как выглядит в коде

useEffect(() => {
  // что сделать
  return () => { /* как убрать за собой */ };
}, [зависимости]);

Три части, и каждая важна:

Тело — что выполнить после отрисовки.

Возвращаемая функция — уборка: отписаться, отменить таймер, прервать запрос. Выполняется перед следующим запуском эффекта и при удалении компонента.

Массив зависимостей — когда повторять. Это и есть источник всех бед.

Массив зависимостей решает всё

ЗаписьКогда выполняется
нет массивапосле каждой отрисовки
[]один раз, при появлении компонента
[userId]при появлении и каждый раз, когда userId изменился

Разница между первой и второй строкой — измеренная:

useEffect(() => {              // массива нет
  setData([...data, "x"]);
});
useEffect(() => {              // пустой массив
  setData(["x"]);
}, []);
измерено
без массива зависимостей:  эффект выполнен раз: 49
с пустым массивом [] :     эффект выполнен раз: 1

Сорок девять раз — и это только потому, что я поставил ограничитель. Без него цикл был бы бесконечным: эффект меняет состояние → компонент перерисовывается → эффект выполняется снова.

Уборка

useEffect(() => {
  const controller = new AbortController();
  fetch(`/api/subs/${userId}`, { signal: controller.signal })
    .then(r => r.json())
    .then(setData)
    .catch(e => { if (e.name !== "AbortError") console.error(e); });
  return () => controller.abort();
}, [userId]);

Зачем нужна возвращаемая функция: пользователь быстро переключил город — первый запрос ещё летит, пошёл второй. Без отмены ответы могут прийти не по порядку, и на экране окажутся данные от предыдущего города.

Это «состояние гонки», и ИИ забывает про него почти всегда: в сгенерированном коде есть fetch, но нет ни AbortController, ни возврата функции уборки.

Когда эффект не нужен

Главное заблуждение: что в useEffect надо класть всё подряд. На деле он нужен только для выхода наружу — сеть, таймеры, подписки, прямая работа с DOM.

Не нужен:

// плохо: лишнее состояние и лишняя перерисовка
const [total, setTotal] = useState(0);
useEffect(() => { setTotal(items.length); }, [items]);

// хорошо: просто вычислить
const total = items.length;

Если значение можно посчитать из того, что уже есть, — считай прямо при отрисовке. Состояние и эффект здесь только добавляют шаг и перерисовку.

Как сказать ИИ

Запрос
В этом useEffect нет массива зависимостей, а внутри меняется
состояние. Исправь и объясни, что было не так.
Запрос
Добавь отмену запроса через AbortController и возврат функции
уборки. Ошибку AbortError не показывай пользователю —
это не ошибка, а отмена.
Запрос
Нужны ли здесь useState и useEffect? Можно ли это значение
просто вычислить при отрисовке?

Последний вопрос стоит задавать всегда, когда видишь useEffect, который только меняет состояние по другим данным.

На что смотреть

Нет массива зависимостей. Проверяется мгновенно, ломает всё.

Массив есть, но неполный. Эффект использует переменную, которой нет в зависимостях, — будет работать со старым значением. Линтер с правилом react-hooks/exhaustive-deps ловит это автоматически.

Нет уборки у подписок и таймеров. setInterval без clearInterval продолжит работать после удаления компонента.

fetch без отмены. Состояние гонки при быстром переключении.

Эффект вместо вычисления. Лишнее состояние, лишняя перерисовка.

Запрос в эффекте без обработки ошибки. Сервер ответил 500 — на экране бесконечная загрузка.

Шпаргалка

  • useEffect — для выхода наружу: сеть, таймеры, подписки.

  • Массив зависимостей: нет — каждый раз, [] — один раз, [x] — при смене x.

  • Нет массива плюс изменение состояния = бесконечный цикл.

  • Возвращай функцию уборки для подписок, таймеров и запросов.

  • fetch без AbortController даёт состояние гонки.

  • Значение считается из имеющихся данных — эффект не нужен.