Что делает useEffect? Когда срабатывает эффект без зависимостей? Для чего нужен массив зависимостей?

👨‍💻 Frontend Developer 🟢 Почти точно будет 🎚️ Легкий
#React #Hooks

Краткий ответ

useEffect — это швейцарский нож для работы со всеми внешними операциями в React-компонентах. Хук, который выносит за скобки JSX всё, что связано с внешним миром: данные, подписки, таймеры и DOM.

🎯 Ключевая особенность — контроль выполнения через массив зависимостей:

Массив зависимостейПоведениеАналог в классах
[]Один раз после монтированияcomponentDidMount
[dep]При изменении зависимостиcomponentDidUpdate
Нет массиваПосле каждого рендера❌ Опасный паттерн
Cleanup функцияУборка перед повторным вызовом/размонтированиемcomponentWillUnmount

Когда он реально нужен:

  • 🚀 Асинхронные операции — загрузка данных, работа с API
  • 🔄 Подписки на события — WebSockets, resize, keyboard events
  • Таймеры и интервалы — setTimeout, setInterval
  • 🎛️ Интеграция со сторонними библиотеками — D3, charts, игровые движки
  • 📊 Ручное управление DOM — когда React недостаточно

Что заменяет из классовых компонентов:

  • componentDidMount
  • componentDidUpdate
  • componentWillUnmount

Полный ответ

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

Что такое useEffect

useEffect — это хук, который принимает функцию, содержащую императивный код, который может изменять состояние приложения или взаимодействовать с внешними API. Эта функция выполняется после каждого рендера компонента (по умолчанию).

Основные характеристики useEffect

  1. Побочные эффекты — выполнение операций, которые выходят за рамки чистого рендеринга
  2. Жизненный цикл — заменяет методы componentDidMount, componentDidUpdate и componentWillUnmount
  3. Зависимости — контроль за частотой выполнения через массив зависимостей
  4. Очистка — возможность возвращать функцию очистки для предотвращения утечек памяти
// Базовый синтаксис useEffect
import { useEffect } from 'react';
 
function MyComponent() {
  useEffect(() => {
    // Побочный эффект
    console.log('Компонент отрендерен');
    
    // Функция очистки (опционально)
    return () => {
      console.log('Компонент будет размонтирован');
    };
  }, []); // Массив зависимостей
  
  return <div>Мой компонент</div>;
}

Когда срабатывает эффект без зависимостей

Эффект без массива зависимостей (когда второй параметр опущен) выполняется после каждого рендера компонента:

import { useEffect, useState } from 'react';
 
function Component() {
  const [count, setCount] = useState(0);
  
  // Этот эффект выполнится после каждого рендера
  useEffect(() => {
    console.log('Эффект выполнен');
    document.title = `Счетчик: ${count}`;
  });
  // Нет массива зависимостей!
  
  return (
    <div>
      <p>Счетчик: {count}</p>
      <button onClick={() => setCount(count + 1)}>
        Увеличить
      </button>
    </div>
  );
}

Особенности эффекта без зависимостей

  1. Выполняется часто — после каждого рендера компонента
  2. Ресурсоемкий — может замедлять приложение при тяжелых операциях
  3. Полезен для — синхронизации с внешними источниками данных

Примеры использования эффекта без зависимостей

// Отслеживание изменений заголовка страницы
function PageTitleUpdater({ title }) {
  useEffect(() => {
    // Обновляем заголовок при каждом рендере
    document.title = title;
  });
  
  return <div>Страница: {title}</div>;
}
 
// Логирование всех изменений состояния
function Logger({ data }) {
  useEffect(() => {
    // Логируем каждое изменение данных
    console.log('Данные изменены:', data);
  });
  
  return <div>Логгер: {JSON.stringify(data)}</div>;
}

Для чего нужен массив зависимостей

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

Типы массивов зависимостей

  1. Без массива — эффект выполняется после каждого рендера
  2. Пустой массив [] — эффект выполняется один раз после первого рендера
  3. Массив с зависимостями [dep1, dep2] — эффект выполняется при изменении любой зависимости
import { useEffect, useState } from 'react';
 
function Component() {
  const [count, setCount] = useState(0);
  const [name, setName] = useState('');
  
  // Выполняется один раз (после первого рендера)
  useEffect(() => {
    console.log('Компонент смонтирован');
    fetchData();
  }, []); // Пустой массив зависимостей
  
  // Выполняется при изменении count
  useEffect(() => {
    console.log('Счетчик изменился:', count);
  }, [count]); // Массив с одной зависимостью
  
  // Выполняется при изменении count или name
  useEffect(() => {
    console.log('Счетчик или имя изменились');
  }, [count, name]); // Массив с двумя зависимостями
  
  return (
    <div>
      <p>Счетчик: {count}</p>
      <button onClick={() => setCount(count + 1)}>
        Увеличить счетчик
      </button>
      <input 
        value={name} 
        onChange={(e) => setName(e.target.value)} 
        placeholder="Введите имя"
      />
    </div>
  );
}

Пустой массив зависимостей

Пустой массив зависимостей [] означает, что эффект не зависит ни от каких переменных и выполнится только один раз:

import { useEffect } from 'react';
 
function Component() {
  // Выполняется один раз после монтирования
  useEffect(() => {
    console.log('Этот код выполнится только один раз');
    
    // Подписка на событие
    const handleResize = () => {
      console.log('Размер окна изменился');
    };
    
    window.addEventListener('resize', handleResize);
    
    // Функция очистки
    return () => {
      window.removeEventListener('resize', handleResize);
      console.log('Подписка отменена');
    };
  }, []); // Пустой массив зависимостей
  
  return <div>Компонент</div>;
}

Массив с зависимостями

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

import { useEffect, useState } from 'react';
 
function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  
  // Выполняется при изменении userId
  useEffect(() => {
    async function fetchUser() {
      const response = await fetch(`/api/users/${userId}`);
      const userData = await response.json();
      setUser(userData);
    }
    
    if (userId) {
      fetchUser();
    }
  }, [userId]); // Зависимость от userId
  
  if (!user) return <div>Загрузка...</div>;
  
  return (
    <div>
      <h1>{user.name}</h1>
      <p>{user.email}</p>
    </div>
  );
}

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

1. Загрузка данных с API

import { useEffect, useState } from 'react';
 
function DataFetcher() {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  
  // Загрузка данных один раз при монтировании
  useEffect(() => {
    async function fetchData() {
      try {
        const response = await fetch('/api/data');
        const result = await response.json();
        setData(result);
      } catch (error) {
        console.error('Ошибка загрузки:', error);
      } finally {
        setLoading(false);
      }
    }
    
    fetchData();
  }, []); // Пустой массив - выполнить один раз
  
  if (loading) return <div>Загрузка...</div>;
  if (!data) return <div>Нет данных</div>;
  
  return (
    <ul>
      {data.map(item => (
        <li key={item.id}>{item.name}</li>
      ))}
    </ul>
  );
}

2. Подписка на события

import { useEffect, useState } from 'react';
 
function WindowSize() {
  const [size, setSize] = useState({
    width: window.innerWidth,
    height: window.innerHeight
  });
  
  // Подписка на изменение размера окна
  useEffect(() => {
    function handleResize() {
      setSize({
        width: window.innerWidth,
        height: window.innerHeight
      });
    }
    
    window.addEventListener('resize', handleResize);
    
    // Очистка подписки при размонтировании
    return () => {
      window.removeEventListener('resize', handleResize);
    };
  }, []); // Выполнить один раз
  
  return (
    <div>
      <p>Ширина: {size.width}px</p>
      <p>Высота: {size.height}px</p>
    </div>
  );
}

3. Таймеры и интервалы

import { useEffect, useState } from 'react';
 
function Timer() {
  const [seconds, setSeconds] = useState(0);
  
  // Запуск таймера
  useEffect(() => {
    const interval = setInterval(() => {
      setSeconds(prev => prev + 1);
    }, 1000);
    
    // Очистка интервала
    return () => {
      clearInterval(interval);
    };
  }, []); // Выполнить один раз
  
  return (
    <div>
      <p>Прошло секунд: {seconds}</p>
    </div>
  );
}

Резюме

useEffect — это мощный инструмент для работы с побочными эффектами в React:

Когда использовать:

  • Для работы с внешними API
  • Для подписки на события
  • Для управления таймерами
  • Для манипуляций с DOM
  • Для выполнения кода при монтировании/размонтировании

Ключевые моменты:

  • Эффект без зависимостей выполняется после каждого рендера
  • Пустой массив зависимостей [] означает однократное выполнение
  • Массив с зависимостями [dep1, dep2] выполняет эффект при их изменении
  • Функция очистки предотвращает утечки памяти
  • Правильное использование зависимостей критично для производительности

Понимание работы useEffect позволяет эффективно управлять побочными эффектами и жизненным циклом компонентов в React-приложениях.


Хотите больше статей для подготовки к собеседованиям? Подписывайтесь на EasyAdvice добавляйте сайт в закладки и совершенствуйтесь каждый день 💪