JS: React
2026-02-26 15:13 Diff

Представим компонент, в котором нужно сделать запрос данных на сервер. Решение этой задачи в лоб может выглядеть как-то так:

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

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

  • В обработчике запроса изменяется стейт
  • Изменение стейта вызывает перерисовку компонента
  • Перерисовка компонента вызывает axios.get()
  • Снова происходит изменение стейта и так по кругу

Никогда не меняйте состояния в рендерах — с этим правилом мы уже знакомились, изучая концепцию MVC.

Выполнить подобные побочные эффекты помогает встроенный хук useEffect(). Именно его мы изучим в этом уроке.

Хук useEffect() заменяет три колбека жизненного цикла:

  • componentDidMount()
  • componentDidUpdate()
  • componentWillUnmount()

Подробнее об их работе можно прочитать в официальной документации.

Начнем с простого примера, в котором используется функция alert(). Вызов этой функции использует API браузера, поэтому он приносит с собой побочные эффекты:

Ниже пример, в котором меняется фон при каждом клике.

Попрактиковаться

Колбек, переданный в useEffect(), отрабатывает после первой отрисовки и каждого обновления компонента. То есть произошло объединение методов componentDidUpdate() и componentDidMount(). Такое изменение было сделано ради удобства. Мировая практика использования React показала, что, в основном, эффекты происходят после каждого рендера, независимо от того, первая эта отрисовка или все последующие. Как бонус, сократилось количество дублирования и кода. Какие типичные сайд эффекты встречаются во фронтенде? Например:

  • Извлечение данных
  • Работа с BOM(Browser Object Model) API, например, Local Storage
  • Прямое изменение DOM, сюда же относятся библиотеки не совместимые с React

Действие хука useEffect() иногда можно пропускать. Такое бывает полезно либо в целях оптимизации, либо, если эффект имеет смысл только при определенных условиях. Для этого в хук вторым аргументом передается массив значений, которые надо отслеживать между отрисовками. Если хотя бы одно значение из этого массива поменялось, то колбек вызывается, если все значения остались прежними — пропускается.

То есть колбек будет вызван только тогда, когда изменится count. Таким же способом можно передать любой набор переменных, который мы бы хотели связать с изменением эффекта. Если хотя бы одна переменная в переданном массиве поменялась, то эффект сработает, иначе React его пропускает.

Что делать, если нужно запустить useEffect() только на момент первого рендера (сразу после монтирования)? Для этого достаточно передать пустой массив:

Решение не самое очевидное, но технически оно не является особым случаем. К нему нужно просто привыкнуть.

Сброс эффекта

В некоторых случаях эффект нужно сбрасывать. Например, когда эффект после изменения пропсов перестает быть актуальным, его нужно «зачистить». Для этого достаточно вернуть функцию из useEffect(), внутри которой выполняется очистка:

Изменение userId приведет к сбросу текущего таймера и установке нового. Подобный код в классах потребовал бы использования аж 4 колбеков жизненного цикла.

Для имитации componentWillUnmount() достаточно соединить очистку с пустым массивом вторым параметром:

Асинхронные запросы

Первым параметром useEffect() принимает функцию. Эта функция должна либо ничего не возвращать, либо возвращать функцию для сброса эффекта. Это накладывает некоторое ограничение на использование async await:

Если использовать async, то функция уже возвращает промис — это нарушает правило выше. Чтобы этого избежать, можно обернуть асинхронный вызов в функцию и вызвать эту функцию внутри useEffect: