useEffect в React — побочные эффекты с примерами

useEffect нужен, когда React-компонент должен синхронизироваться с внешним миром: API, таймером, подпиской, localStorage, WebSocket или DOM API. Если внешней системы нет, часто Effect вообще не нужен.

Базовые варианты useEffect

useEffect проще понять через несколько типовых случаев: запуск после рендера, зависимости, запрос к API и очистка таймера. Главное — помнить, что effect нужен для синхронизации с внешней системой, а не для любого вычисления.

Если каждый пример привязать к реальной внешней системе, hook становится намного понятнее. Запрос к API, таймер, подписка и работа с DOM требуют разных деталей, но общий принцип синхронизации один.

Базовый синтаксис

import { useEffect } from 'react';

useEffect(() => {
  // код эффекта
  return () => {
    // очистка эффекта
  };
}, [dependencies]);

Три варианта зависимостей

ЗаписьКогда сработает
useEffect(fn)После каждого рендера
useEffect(fn, [])После первого монтирования
useEffect(fn, [id])После первого монтирования и при изменении id

Пример: запрос к API

import { useEffect, useState } from 'react';

function Users() {
  const [users, setUsers] = useState([]);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    let ignore = false;

    async function loadUsers() {
      setLoading(true);
      const response = await fetch('https://jsonplaceholder.typicode.com/users');
      const data = await response.json();
      if (!ignore) {
        setUsers(data);
        setLoading(false);
      }
    }

    loadUsers();

    return () => {
      ignore = true;
    };
  }, []);

  if (loading) return <p>Загрузка...</p>;

  return users.map(user => <p key={user.id}>{user.name}</p>);
}

Флаг ignore защищает от ситуации, когда компонент уже исчез со страницы, а запрос вернулся позже. Для реальных проектов удобнее использовать TanStack Query, но для понимания useEffect пример с fetch полезен.

Пример: таймер с очисткой

function Timer() {
  const [seconds, setSeconds] = useState(0);

  useEffect(() => {
    const id = setInterval(() => {
      setSeconds(s => s + 1);
    }, 1000);

    return () => clearInterval(id);
  }, []);

  return <p>Прошло секунд: {seconds}</p>;
}

Когда useEffect не нужен

Не используйте useEffect для вычисления данных, которые можно получить прямо во время рендера. Например, fullName не нужно хранить в отдельном state и обновлять через effect.

// Лишний Effect
const [fullName, setFullName] = useState('');

useEffect(() => {
  setFullName(firstName + ' ' + lastName);
}, [firstName, lastName]);

// Лучше так
const fullName = firstName + ' ' + lastName;

Strict Mode и двойной запуск

В режиме разработки React может запускать effect и cleanup дважды, чтобы найти ошибки очистки. В production этого дополнительного цикла нет. Если двойной запуск ломает логику, скорее всего effect не очищает подписку, таймер или внешний ресурс.

Не отключайте Strict Mode только ради того, чтобы скрыть двойной запуск. Лучше убедиться, что запросы отменяются, подписки очищаются, таймеры снимаются, а повторный запуск effect не ломает состояние приложения.

Пример: поиск с отменой запроса

Одна из частых задач — отправлять запрос при изменении поисковой строки. Здесь важно не только загрузить данные, но и не показать устаревший ответ, если пользователь уже ввёл новый запрос.

function SearchUsers({ query }) {
  const [users, setUsers] = useState([]);
  const [loading, setLoading] = useState(false);

  useEffect(() => {
    if (!query.trim()) {
      setUsers([]);
      return;
    }

    const controller = new AbortController();

    async function search() {
      setLoading(true);
      try {
        const response = await fetch(`/api/users?q=${query}`, {
          signal: controller.signal,
        });
        const data = await response.json();
        setUsers(data);
      } finally {
        setLoading(false);
      }
    }

    search();

    return () => controller.abort();
  }, [query]);

  if (loading) return <p>Ищем...</p>;
  return users.map(user => <p key={user.id}>{user.name}</p>);
}

AbortController отменяет предыдущий запрос, когда query изменился или компонент исчез со страницы. Это делает интерфейс предсказуемее при быстром вводе.

Чек-лист useEffect

  • Effect подключается к внешней системе?
  • Все значения из компонента есть в массиве зависимостей?
  • Нужна ли cleanup-функция?
  • Можно ли заменить effect обычным вычислением во время рендера?
  • Не создаёт ли effect бесконечный цикл setState?

Как не попасть в бесконечный цикл

Если effect меняет state, а этот state находится в зависимостях, компонент может рендериться бесконечно. Проверьте, действительно ли setState нужен внутри effect, или значение можно вычислить прямо в JSX.

Что проверить, если useEffect ведёт себя странно

useEffect чаще всего ломается не из-за React, а из-за неправильного ожидания: разработчик хочет «выполнить код после рендера», хотя на самом деле нужно синхронизироваться с внешней системой.

Полезно сначала убрать effect и спросить: можно ли получить это значение прямо из props или state во время рендера? Если да, effect не нужен. Если нет, проверьте зависимости и cleanup.

Быстрая диагностика

  • если effect запускается бесконечно, проверьте setState и зависимости;
  • если запускается два раза в разработке, проверьте Strict Mode;
  • если запросы приходят не в том порядке, добавьте отмену или TanStack Query;
  • если effect только вычисляет значение из props, он не нужен;
  • если есть подписка или таймер, должна быть cleanup-функция.

Вопрос, который экономит время

Спросите себя: «С какой внешней системой я синхронизируюсь?» Если ответа нет, возможно, useEffect здесь лишний. fullName, отфильтрованный массив и derived state обычно считаются прямо во время рендера.

Следующее упражнение

Сделайте поиск пользователей по query с AbortController, затем перепишите тот же пример на TanStack Query. После сравнения станет ясно, где заканчивается учебный useEffect и начинается нормальная работа с серверными данными.

Оцените статью
0 0 голоса
Рейтинг статьи
Подписаться
Уведомить о
guest

0 комментариев
Старые
Новые Популярные
Межтекстовые Отзывы
Посмотреть все комментарии
0
Оставьте комментарий! Напишите, что думаете по поводу статьи.x