Перейти к содержанию

exhaustive-deps

Проверяет, что массивы зависимостей хуков React содержат все нужные зависимости.

Подробности правила

Хуки React вроде useEffect, useMemo и useCallback принимают массивы зависимостей. Если значение, на которое есть ссылка внутри такого хука, не входит в массив зависимостей, React не перезапустит эффект и не пересчитает значение, когда эта зависимость изменится. Из-за этого возникают устаревшие замыкания: хук использует устаревшие значения.

Типичные нарушения

Эта ошибка часто появляется, когда React пытаются «обмануть» насчёт зависимостей, чтобы управлять тем, когда запускается эффект. Эффекты должны синхронизировать компонент с внешними системами. Массив зависимостей сообщает React, какие значения использует эффект, чтобы React знал, когда синхронизировать его заново.

Если вы спорите с линтом, код, скорее всего, нужно перестроить. Как это сделать, см. в Удаление зависимостей эффектов.

Неверно

Примеры неверного кода для этого правила:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// ❌ Missing dependency
useEffect(() => {
  console.log(count);
}, []); // Missing 'count'

// ❌ Missing prop
useEffect(() => {
  fetchUser(userId);
}, []); // Missing 'userId'

// ❌ Incomplete dependencies
useMemo(() => {
  return items.sort(sortOrder);
}, [items]); // Missing 'sortOrder'

Верно

Примеры верного кода для этого правила:

1
2
3
4
5
6
7
8
9
// ✅ All dependencies included
useEffect(() => {
  console.log(count);
}, [count]);

// ✅ All dependencies included
useEffect(() => {
  fetchUser(userId);
}, [userId]);

Устранение неполадок

Зависимость-функция вызывает бесконечный цикл

Есть эффект, но на каждом рендере создаётся новая функция:

1
2
3
4
5
6
7
8
// ❌ Causes infinite loop
const logItems = () => {
  console.log(items);
};

useEffect(() => {
  logItems();
}, [logItems]); // Infinite loop!

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// ✅ Call it from the event handler
const logItems = () => {
  console.log(items);
};

return <button onClick={logItems}>Log</button>;

// ✅ Or derive during render if there's no side effect
items.forEach(item => {
  console.log(item);
});

Если эффект действительно нужен (например, чтобы на что-то подписаться снаружи), сделайте зависимость стабильной:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
// ✅ useCallback keeps the function reference stable
const logItems = useCallback(() => {
  console.log(items);
}, [items]);

useEffect(() => {
  logItems();
}, [logItems]);

// ✅ Or move the logic straight into the effect
useEffect(() => {
  console.log(items);
}, [items]);

Запуск эффекта только один раз

Нужно запустить эффект один раз при монтировании, но линт сообщает о пропущенной зависимости:

1
2
3
4
// ❌ Missing dependency
useEffect(() => {
  sendAnalytics(userId);
}, []); // Missing 'userId'

Либо включите зависимость (так и стоит делать), либо используйте реф, если запуск действительно должен быть однократным:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
// ✅ Include dependency
useEffect(() => {
  sendAnalytics(userId);
}, [userId]);

// ✅ Or use a ref guard inside an effect
const sent = useRef(false);

useEffect(() => {
  if (sent.current) {
    return;
  }

  sent.current = true;
  sendAnalytics(userId);
}, [userId]);

Параметры

Пользовательские хуки-эффекты можно настроить через общие параметры ESLint (доступно в eslint-plugin-react-hooks 6.1.1 и новее):

1
2
3
4
5
6
7
{
  "settings": {
    "react-hooks": {
      "additionalEffectHooks": "(useMyEffect|useCustomEffect)"
    }
  }
}
  • additionalEffectHooks: регулярное выражение для пользовательских хуков, у которых нужно проверять полноту зависимостей. Эта настройка общая для всех правил react-hooks.

Для обратной совместимости правило также принимает параметр на уровне самого правила:

1
2
3
4
5
6
7
{
  "rules": {
    "react-hooks/exhaustive-deps": ["warn", {
      "additionalHooks": "(useMyCustomHook|useAnotherHook)"
    }]
  }
}
  • additionalHooks: регулярное выражение для хуков, у которых нужно проверять полноту зависимостей. Примечание: если задан этот параметр уровня правила, он важнее общей конфигурации settings.

Источник — https://react.dev/reference/eslint-plugin-react-hooks/lints/exhaustive-deps

Комментарии