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

Какие знаешь виды зависимостей Hooks?

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

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

🐱
claude-haiku-4.5PrepBro AI26 мар. 2026 г.(ред.)

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

Виды зависимостей в React Hooks

Массив зависимостей (dependency array) в Hooks — это критический механизм для контроля когда эффект выполняется. Существует три основных вида зависимостей и правила для работы с ними.

1. Пустой массив зависимостей [] — выполнить один раз

Эффект выполнится только один раз при монтировании компонента и очистится при размонтировании.

function MyComponent() {
  useEffect(() => {
    console.log('Компонент смонтирован');
    
    // Очистка (опционально)
    return () => {
      console.log('Компонент размонтирован');
    };
  }, []); // Пустой массив — только один раз
  
  return <div>Hello</div>;
}

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

  • Инициализация данных
  • Подписка на события (и отписка при размонтировании)
  • Загрузка начальных данных с API
// Практический пример: загрузка данных
function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  
  useEffect(() => {
    fetchUser(userId)
      .then(setUser);
  }, []); // Загружается один раз
  
  return <div>{user?.name}</div>;
}

Опасность: Если компонент переиспользуется с разными props, эффект не обновится!

2. Без массива зависимостей (undefined) — выполнить всегда

Эффект выполнится после каждого рендера (после каждого изменения props или state).

function MyComponent() {
  const [count, setCount] = useState(0);
  
  useEffect(() => {
    console.log('Эффект после каждого рендера');
  }); // Без массива — выполняется всегда
  
  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>+1</button>
    </div>
  );
}

// При каждом клике — логируется два раза
// 1. После клика (setCount)
// 2. После рендера

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

  • Синхронизация с DOM (редко в современном React)
  • Отладка

Проблема: Это очень редко нужно и обычно вызывает performance issues.

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

Эффект выполнится только если одна из зависимостей изменилась. React сравнивает через Object.is().

function SearchUsers({ query }) {
  const [results, setResults] = useState([]);
  
  useEffect(() => {
    console.log('Ищу пользователей для:', query);
    searchAPI(query)
      .then(setResults);
  }, [query]); // Выполняется только если query изменился
  
  return <ul>{results.map(r => <li key={r.id}>{r.name}</li>)}</ul>;
}

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

function UserProfile({ userId, includeDetails }) {
  const [user, setUser] = useState(null);
  
  useEffect(() => {
    const endpoint = includeDetails 
      ? `/api/users/${userId}/full`
      : `/api/users/${userId}`;
    
    fetch(endpoint)
      .then(r => r.json())
      .then(setUser);
  }, [userId, includeDetails]); // Обе зависимости
  // Выполнится если userId ИЛИ includeDetails изменился
  
  return <div>{user?.name}</div>;
}

Как React сравнивает зависимости?

React использует Object.is() для сравнения:

// Примитивы сравниваются по значению
Object.is(5, 5) // true
Object.is('hello', 'hello') // true
Object.is(true, true) // true
Object.is(null, null) // true
Object.is(undefined, undefined) // true

// Объекты сравниваются по ссылке (identity)
const obj1 = { name: 'John' };
const obj2 = { name: 'John' };
Object.is(obj1, obj2) // false — разные объекты!
Object.is(obj1, obj1) // true — один объект

Практическая проблема с объектами:

function MyComponent() {
  const [count, setCount] = useState(0);
  
  // ❌ Проблема: новый объект при каждом рендере
  const config = { enabled: true, value: count };
  
  useEffect(() => {
    console.log('Config изменился:', config);
  }, [config]); // Выполнится при КАЖДОМ рендере
  // Потому что config — новый объект каждый раз!
  
  return (
    <div>
      Count: {count}
      <button onClick={() => setCount(count + 1)}>+1</button>
    </div>
  );
}

// ✅ Решение 1: переместить объект выше
const config = { enabled: true }; // Вне компонента

function MyComponent() {
  const [count, setCount] = useState(0);
  
  useEffect(() => {
    console.log('Config изменился:', config);
  }, [config]); // Теперь config одинаковый
}

// ✅ Решение 2: useMemo для стабилизации
function MyComponent() {
  const [count, setCount] = useState(0);
  
  const config = useMemo(() => ({
    enabled: true,
    value: count
  }), [count]); // Только если count изменился
  
  useEffect(() => {
    console.log('Config изменился:', config);
  }, [config]); // Только если config по-настоящему изменился
  
  return <div>Count: {count}</div>;
}

Проблемы с зависимостями и их решения

Проблема 1: Забытая зависимость

// ❌ Ошибка
function Timer() {
  const [seconds, setSeconds] = useState(0);
  
  useEffect(() => {
    const interval = setInterval(() => {
      setSeconds(s => s + 1); // Забыли включить в зависимости
    }, 1000);
    
    return () => clearInterval(interval);
  }, []); // Пусто, но seconds используется!
  
  return <div>Time: {seconds}</div>;
}

// ✅ Исправить: использовать функциональный update
function Timer() {
  const [seconds, setSeconds] = useState(0);
  
  useEffect(() => {
    const interval = setInterval(() => {
      setSeconds(s => s + 1); // Функция не нуждается в зависимостях
    }, 1000);
    
    return () => clearInterval(interval);
  }, []); // Правильно!
  
  return <div>Time: {seconds}</div>;
}

Проблема 2: Лишние зависимости

// ❌ Избыточно
function Component() {
  const [count, setCount] = useState(0);
  const double = count * 2;
  
  useEffect(() => {
    console.log('Double changed:', double);
  }, [count, double]); // double зависит от count!
  // Лучше включить только count
}

// ✅ Правильно
function Component() {
  const [count, setCount] = useState(0);
  const double = count * 2;
  
  useEffect(() => {
    console.log('Count changed:', count);
    // Вычисляем double внутри
    const d = count * 2;
  }, [count]); // Только count
}

Проблема 3: Стабилизация с useCallback

function Parent() {
  const [count, setCount] = useState(0);
  
  // ❌ Новая функция при каждом рендере
  const handleClick = () => {
    console.log('Count:', count);
  };
  
  return <Child onClick={handleClick} />; // Child переренда
}

// ✅ useCallback для стабилизации
function Parent() {
  const [count, setCount] = useState(0);
  
  const handleClick = useCallback(() => {
    console.log('Count:', count);
  }, [count]); // Новая функция только если count изменился
  
  return <Child onClick={handleClick} />;
}

Правило ESLint: exhaustive-deps

ESLint правило react-hooks/exhaustive-deps поможет найти ошибки:

// Правило будет ругаться
useEffect(() => {
  console.log(user); // ← используется
}, [userId]); // ← но user не в зависимостях!

// Решение
useEffect(() => {
  console.log(user);
}, [user]); // Или [userId] если user вычисляется из userId

Резюме

ЗависимостиКогда выполняетсяИспользование
[]Один раз при монтированииИнициализация, подписки
undefinedПосле каждого рендераРедко, обычно ошибка
[dep1, dep2]Если dep измениласьОсновной случай

Правила:

  1. Включай все переменные, которые используются в эффекте
  2. Не забывай про Object.is() — объекты сравниваются по ссылке
  3. Используй функциональные updates чтобы избежать зависимостей от state
  4. Включи ESLint правило exhaustive-deps
  5. Используй useMemo и useCallback для стабилизации зависимостей