Gernar
Frontend DeveloperReact

Интервью-вопрос

Что если не передавать второй параметр в useEffect

Без массива зависимостей useEffect запускается после каждого рендера. Это может быть намеренным поведением, но часто приводит к лишним запросам, циклам обновления состояния и забытым cleanup.

Добавлен
Редакция

Подготовьте короткий ответ и пару деталей на случай уточняющих вопросов.

🐰0
🥚0

Мини-квиз

Проверка перед разбором

Несколько быстрых вопросов перед разбором. Так проще поймать места, которые только кажутся понятными.

Вопрос 1 из 50 правильно

Что ответить про useEffect без второго аргумента?

Вы отвечаете именно про отсутствие массива зависимостей, а не про пустой массив.

Варианты ответа

Разбор

Разобраться, а не зазубрить

Дальше разбираем суть, типичные уточнения и места, где легко сказать лишнее или перепутать термины.

Базовая идея

Второй аргумент useEffect говорит React, когда эффект можно пропустить. Если аргумента нет, условия для пропуска нет. Поэтому эффект запускается после каждого рендера.

Важно не перепутать причину и следствие. Эффект сам по себе не заставляет компонент рендериться. Рендер вызывают изменение state, новые props, обновление родителя или другая причина. Но после каждого такого рендера React снова выполнит эффект.

Короткий ответ для интервью:

Без второго параметра useEffect выполняется после каждого рендера. Это нормально только если эффект действительно должен синхронизироваться с любым обновлением. В остальных случаях я указываю зависимости или пустой массив, чтобы не получить лишние сайд-эффекты.

Чем это отличается от пустого массива

Частая ловушка в вопросе. Отсутствие второго аргумента и пустой массив это разные режимы.

useEffect(() => {
  console.log("after every render");
});

useEffect(() => {
  console.log("after mount");
}, []);

Первый эффект запустится после каждого рендера. Второй запустится после монтирования, а его cleanup сработает при размонтировании. В development с React Strict Mode вы можете увидеть дополнительный запуск для проверки безопасности эффекта. Это отдельный нюанс режима разработки.

Без массива зависимостей
  1. 1Компонент рендерится
  2. 2Браузер обновляет экран
  3. 3React запускает эффект
  4. 4Следующий рендер снова запускает этот эффект
С явными зависимостями
  1. 1Компонент рендерится
  2. 2React сравнивает зависимости
  3. 3Эффект запускается только при изменении нужных значений
  4. 4Cleanup очищает прошлую подписку или запрос

Главная опасность: эффект сам вызывает новый рендер

Плохой пример. Эффект без массива зависимостей каждый раз меняет state.

function Counter() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    setCount(count + 1);
  });

  return <p>{count}</p>;
}

Что сломается. Эффект выполнится после рендера и вызовет setCount. React сделает новый рендер, потом снова выполнит эффект. Так можно получить бесконечный цикл обновлений.

Если нужно обновить состояние внутри эффекта, нужно понятное условие, правильные зависимости или другой способ вычисления значения. Иногда state вообще не нужен, потому что значение можно посчитать во время рендера.

Как выбирать зависимости

На интервью хорошо звучит не только правило, но и критерий выбора. Спросите себя, с чем эффект синхронизирует компонент. Это может быть DOM, сервер, подписка, таймер, внешнее хранилище или значение из props и state.

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

Как выбрать второй аргумент

1Эффект должен синхронизироваться после любого рендера?
Можно не передавать массив, но проверьте стоимость эффекта и cleanup.
2Эффект нужен только при mount и cleanup при unmount?
Используйте пустой массив, но не прячьте внутри изменяемые props и state.
3Эффект читает props, state, функцию или значение из компонента?
Укажите их в массиве зависимостей или стабилизируйте значение осознанно.
4Эффект делает запрос, подписку или таймер?
Добавьте cleanup, отмену устаревшего запроса и явные состояния loading, error и empty state.

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

Плохой пример. Запрос без массива зависимостей.

function Profile({ userId }) {
  const [profile, setProfile] = useState(null);

  useEffect(() => {
    fetch(`/api/users/${userId}`)
      .then((response) => response.json())
      .then(setProfile);
  });

  return <ProfileCard profile={profile} />;
}

Здесь любой рендер снова отправит запрос. Если setProfile обновит состояние, компонент снова отрендерится и может снова сделать запрос. Пользователь увидит мигающий профиль или старые данные. Сеть получит лишнюю нагрузку. Медленный ответ для старого userId может перетереть новый экран.

Более безопасный вариант:

function Profile({ userId }) {
  const [profile, setProfile] = useState(null);
  const [status, setStatus] = useState("idle");
  const [error, setError] = useState(null);

  useEffect(() => {
    const controller = new AbortController();

    setStatus("loading");
    setError(null);

    fetch(`/api/users/${userId}`, { signal: controller.signal })
      .then((response) => {
        if (!response.ok) {
          throw new Error("Failed to load profile");
        }

        return response.json();
      })
      .then((data) => {
        setProfile(data);
        setStatus("success");
      })
      .catch((error) => {
        if (error.name === "AbortError") {
          return;
        }

        setError(error);
        setStatus("error");
      });

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

  if (status === "loading") return <Spinner />;
  if (status === "error") return <ErrorMessage error={error} />;
  if (!profile) return <EmptyState />;

  return <ProfileCard profile={profile} />;
}

Теперь эффект запускается при первом рендере и при изменении userId. Cleanup отменяет устаревший запрос, поэтому результат старого запроса не должен перетереть новый экран. UI явно показывает загрузку, ошибку и пустое состояние. Пользователь не остается со старой карточкой без объяснения.

Когда отсутствие второго параметра допустимо

Такой код не всегда ошибка. Иногда эффект действительно должен запускаться после каждого рендера. Например, вы измеряете состояние внешней интеграции, пишете отладочный лог рендеров или синхронизируете что-то, что нельзя выразить через конкретный набор зависимостей.

Но в ответе лучше подчеркнуть цену решения. Эффект должен быть быстрым, идемпотентным и безопасным при повторном запуске. Если он создает внешний ресурс, нужен cleanup. Если он меняет state, нужно условие. Иначе появится цикл.

Что сказать коротко на интервью

Хорошая формулировка:

Если второй параметр не передать, useEffect будет выполняться после каждого рендера. Это отличается от пустого массива, где эффект привязан к монтированию. Поэтому для запросов, подписок и таймеров я обычно явно задаю зависимости и cleanup. Без массива оставляю эффект только тогда, когда он действительно должен реагировать на любое обновление и безопасен при повторном запуске.

Такой ответ закрывает базовое поведение, практический риск и показывает, что вы не лечите все эффекты пустым массивом.

Частые ошибки

Где обычно ошибаются

Проверьте формулировки, которые звучат уверенно, но на интервью быстро выдают пробелы.

  1. 1

    Путать отсутствие массива с пустым массивом

    Без второго аргумента эффект запускается после каждого рендера. Пустой массив означает запуск после монтирования и cleanup при размонтировании. Если перепутать эти режимы, пример с setState внутри эффекта быстро покажет ошибку.
  2. 2

    Делать запрос после каждого рендера

    Запрос без зависимостей может уйти снова после любого изменения состояния, даже после ввода в поле формы. Это дает лишнюю нагрузку, мигающий UI и race condition. Для загрузки по параметру укажите параметр в зависимостях и отменяйте устаревший запрос.
  3. 3

    Обновлять state без условия

    Если эффект каждый раз вызывает setState, React снова рендерит компонент и снова запускает эффект. Так появляется бесконечный цикл. Безопаснее добавить условие, вычислить значение во время рендера или указать правильные зависимости.
  4. 4

    Прятать зависимости пустым массивом

    Пустой массив не исправляет проблему, если эффект читает изменяемые props или state. Он просто фиксирует значения из первого рендера. Лучше указать зависимости или перестроить код так, чтобы эффект не зависел от устаревшего замыкания.
  5. 5

    Забывать cleanup для подписок и таймеров

    При повторном запуске эффекта старая подписка может остаться активной, если ее не очистить. Это приводит к дублирующим обработчикам, утечкам памяти и странным обновлениям UI. Возвращайте функцию очистки из useEffect.

Follow-up

Что могут спросить дальше

Короткие ответы на вопросы, которыми проверяют понимание useEffect, зависимостей и безопасных сайд-эффектов.

Живые ответы

Видео с похожим вопросом

Если найдем публичные интервью с таким вопросом, добавим их сюда. Их удобно смотреть после теории, чтобы свериться с живыми ответами.

Пока видео нет. Когда появятся подходящие публичные интервью, добавим их в этот блок, чтобы можно было сравнить разбор с тем, как отвечают реальные кандидаты.

Содержание