← Назад к вопросам

Для чего нужен второй аргумент в UseEffect?

2.0 Middle🔥 241 комментариев
#React

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI2 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Для чего нужен второй аргумент в useEffect

Второй аргумент в useEffect — это массив зависимостей (dependency array). Он контролирует когда эффект должен быть заново запущен. Это один из самых важных концептов в React, потому что неправильное использование может привести к баги и бесконечным циклам.

Синтаксис useEffect

// Базовый синтаксис
useEffect(() => {
  // Этот код выполняется
}, [dependencies]); // Второй аргумент - зависимости

// Он работает как:
// "Выполни этот эффект когда какая-то из зависимостей изменилась"

Три варианта использования

1. Без второго аргумента — срабатывает при каждом рендере

import { useState, useEffect } from 'react';

export default function Counter() {
  const [count, setCount] = useState(0);
  
  useEffect(() => {
    console.log('Эффект срабатывает при КАЖДОМ рендере');
    document.title = `Count: ${count}`;
    // Это будет выполняться после каждого рендера
  }); // НЕТ second аргумента
  
  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  );
}

// Клик на кнопку -> рендер -> эффект выполнен
// Еще клик -> рендер -> эффект выполнен снова
// И так каждый раз

Проблема: Может быть очень неэффективно и привести к бесконечным циклам.

2. Пустой массив [] — срабатывает только при монтировании

import { useState, useEffect } from 'react';

export default function UserProfile() {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);
  
  useEffect(() => {
    console.log('Эффект срабатывает ТОЛЬКО ОДИН РАЗ при монтировании');
    
    // Загружаем данные пользователя
    fetch('/api/user')
      .then(res => res.json())
      .then(data => {
        setUser(data);
        setLoading(false);
      });
  }, []); // Пустой массив зависимостей
  
  return (
    <div>
      {loading ? <p>Loading...</p> : <h1>{user.name}</h1>}
    </div>
  );
}

// Монтирование компонента -> эффект выполнен (загрузка данных)
// Перерендер -> эффект НЕ выполняется
// Размонтирование -> cleanup (если есть)

Идеально для: Инициализации данных, установки слушателей, подписок.

3. С зависимостями [dep1, dep2] — срабатывает когда зависимости изменились

import { useState, useEffect } from 'react';

export default function SearchResults() {
  const [query, setQuery] = useState('');
  const [results, setResults] = useState([]);
  
  useEffect(() => {
    console.log('Эффект срабатывает когда query изменился');
    
    if (query.trim() === '') {
      setResults([]);
      return;
    }
    
    // Выполнить поиск
    fetch(`/api/search?q=${query}`)
      .then(res => res.json())
      .then(data => setResults(data));
  }, [query]); // Зависимость от query
  
  return (
    <div>
      <input
        value={query}
        onChange={(e) => setQuery(e.target.value)}
        placeholder="Search..."
      />
      <ul>
        {results.map(item => <li key={item.id}>{item.name}</li>)}
      </ul>
    </div>
  );
}

// query = '' -> монтирование -> эффект (нет поиска)
// Ввод 'react' -> query изменился -> эффект (поиск 'react')
// Ввод 'reacts' -> query изменился -> эффект (новый поиск)

Глубокое объяснение зависимостей

// Как React сравнивает зависимости
const dependencies = [query, userId, token];

// При каждом рендере React делает:
if (
  previousDependencies[0] !== dependencies[0] || // query
  previousDependencies[1] !== dependencies[1] || // userId
  previousDependencies[2] !== dependencies[2]    // token
) {
  // Эффект должен быть заново выполнен
  effect();
}

// Сравнение идет через === (strict equality)
// Примитивы сравниваются по значению
// Объекты сравниваются по ссылке

Частая ошибка: зависимости от объектов

import { useState, useEffect } from 'react';

// ПЛОХО - объект создается каждый рендер
export default function BadExample() {
  const [user, setUser] = useState(null);
  
  // config создается при каждом рендере - новая ссылка
  const config = {
    method: 'GET',
    headers: { 'Authorization': 'Bearer token' }
  };
  
  useEffect(() => {
    // Эффект будет выполняться бесконечно!
    // config всегда новый объект -> зависимость изменилась
    fetch('/api/user', config)
      .then(res => res.json())
      .then(data => setUser(data));
  }, [config]); // Плохо!
  
  return <div>{user?.name}</div>;
}

// Результат:
// 1. Рендер -> эффект -> fetch -> setState
// 2. Новый рендер -> config новый объект -> эффект -> fetch -> setState
// 3. Бесконечный цикл!

// ХОРОШО - объект вне эффекта или useMemo
function GoodExample() {
  const [user, setUser] = useState(null);
  
  // Вариант 1: Константа вне компонента
  useEffect(() => {
    fetch('/api/user', CONFIG)
      .then(res => res.json())
      .then(data => setUser(data));
  }, []); // Нет зависимостей
  
  return <div>{user?.name}</div>;
}

// Вариант 2: useMemo для стабильной ссылки
import { useMemo } from 'react';

function BetterExample() {
  const [user, setUser] = useState(null);
  const [token, setToken] = useState('initial-token');
  
  const config = useMemo(() => ({
    method: 'GET',
    headers: { 'Authorization': `Bearer ${token}` }
  }), [token]); // Только перестраивается при изменении token
  
  useEffect(() => {
    fetch('/api/user', config)
      .then(res => res.json())
      .then(data => setUser(data));
  }, [config]); // Теперь безопасно
  
  return <div>{user?.name}</div>;
}

Практические примеры

Пример 1: Загрузка данных при изменении ID

import { useState, useEffect } from 'react';

function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);
  
  useEffect(() => {
    setLoading(true);
    setError(null);
    
    fetch(`/api/users/${userId}`)
      .then(res => {
        if (!res.ok) throw new Error('Failed to fetch');
        return res.json();
      })
      .then(data => {
        setUser(data);
        setLoading(false);
      })
      .catch(err => {
        setError(err.message);
        setLoading(false);
      });
  }, [userId]); // Перезагрузить при изменении userId
  
  if (loading) return <p>Loading...</p>;
  if (error) return <p>Error: {error}</p>;
  return <h1>{user?.name}</h1>;
}

// userId = 1 -> эффект -> загрузка пользователя 1
// userId = 2 -> эффект -> загрузка пользователя 2

Пример 2: Cleanup - удаление слушателя

import { useState, useEffect } from 'react';

function WindowSize() {
  const [width, setWidth] = useState(window.innerWidth);
  
  useEffect(() => {
    const handleResize = () => {
      setWidth(window.innerWidth);
    };
    
    // Добавляем слушатель
    window.addEventListener('resize', handleResize);
    
    // Cleanup функция - удаляется слушатель
    return () => {
      console.log('Cleanup: удаляем слушатель');
      window.removeEventListener('resize', handleResize);
    };
  }, []); // Добавляем/удаляем слушатель только один раз
  
  return <p>Window width: {width}</p>;
}

// Монтирование:
// -> эффект выполнен (слушатель добавлен)
// -> перерендеры: слушатель остается
// Размонтирование:
// -> cleanup выполнен (слушатель удален)

Пример 3: Множественные зависимости

function ComplexEffect() {
  const [data, setData] = useState(null);
  const [filter, setFilter] = useState('');
  const [sort, setSort] = useState('asc');
  
  useEffect(() => {
    // Выполняется когда filter ИЛИ sort изменились
    const url = `/api/data?filter=${filter}&sort=${sort}`;
    fetch(url)
      .then(res => res.json())
      .then(data => setData(data));
  }, [filter, sort]); // Зависимости
  
  return (
    <div>
      <input
        value={filter}
        onChange={(e) => setFilter(e.target.value)}
      />
      <select value={sort} onChange={(e) => setSort(e.target.value)}>
        <option value="asc">Ascending</option>
        <option value="desc">Descending</option>
      </select>
      <pre>{JSON.stringify(data, null, 2)}</pre>
    </div>
  );
}

Таблица сценариев

Второй аргументКогда срабатываетПримеры использования
ОтсутствуетПри каждом рендереРедко используется (может быть неэффективно)
[]Один раз при монтированииЗагрузка данных, инициализация
[dep]При монтировании и когда dep изменилсяОтслеживание изменений параметров
[dep1, dep2]При монтировании и когда dep1 или dep2 изменилисьЗависит от нескольких переменных

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

✅ ПРАВИЛЬНО:
// 1. Включай все переменные из scope которые используются
useEffect(() => {
  console.log(count); // используешь count
}, [count]); // count в зависимостях

// 2. Используй пустой массив если не нужны перезапуски
useEffect(() => {
  // Инициализирующая логика
}, []); // Один раз

// 3. Если нужна стабильная ссылка на объект, используй useMemo
const config = useMemo(() => ({ token }), [token]);
useEffect(() => {
  fetch('/api', config);
}, [config]);

❌ НЕПРАВИЛЬНО:
// 1. Забыл включить зависимость
const [count, setCount] = useState(0);
useEffect(() => {
  console.log(count); // используешь count
}); // Забыл [count] -> эффект при каждом рендере

// 2. Объект в зависимостях
const config = { /* ... */ };
useEffect(() => {
  // ...
}, [config]); // config новый каждый раз -> бесконечный цикл

// 3. Функция в зависимостях
const handleClick = () => { /* ... */ };
useEffect(() => {
  button.addEventListener('click', handleClick);
  return () => button.removeEventListener('click', handleClick);
}, [handleClick]); // handleClick новая каждый раз

Инструменты для проверки

// ESLint hook для проверки зависимостей
// npm install --save-dev eslint-plugin-react-hooks

// .eslintrc.json
{
  "extends": ["react-app"],
  "plugins": ["react-hooks"],
  "rules": {
    "react-hooks/rules-of-hooks": "error",
    "react-hooks/exhaustive-deps": "warn" // Проверяет зависимости
  }
}

Заключение

Второй аргумент useEffect (dependency array) критически важен для:

  • Контроля когда эффект запускается
  • Избежания бесконечных циклов
  • Оптимизации производительности

Помни:

  • Нет аргумента = при каждом рендере (редко нужно)
  • [] = только при монтировании (для инициализации)
  • [deps] = при монтировании и изменении deps (самый частый случай)
Для чего нужен второй аргумент в UseEffect? | PrepBro