知海

规则:exhaustive-deps

ReactAPI 参考:ESLint 插件

exhaustive-deps

验证 React hooks 的依赖数组是否包含所有必要的依赖。

规则详情 {/rule-details/}

useEffectuseMemouseCallback 等 React hooks 接受依赖数组。当这些 hooks 内部引用的值未包含在依赖数组中时,React 不会在该依赖变化时重新运行 effect 或重新计算值。这会导致闭包过期(stale closure),使 hook 使用过时的值。

常见违规 {/common-violations/}

这种错误通常发生在你试图“欺骗” React 的依赖关系以控制 effect 何时运行时。Effects 应该将你的组件与外部系统同步。依赖数组告诉 React effect 使用了哪些值,以便 React 知道何时需要重新同步。

如果你发现自己与 linter 对抗,很可能需要重构代码。请参阅移除 Effect 依赖了解如何操作。

无效用法 {/invalid/}

本规则的错误代码示例:

js 复制代码
// ❌ 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'

有效用法 {/valid/}

本规则的正确代码示例:

js 复制代码
// ✅ All dependencies included
useEffect(() => {
  console.log(count);
}, [count]);

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

故障排除 {/troubleshooting/}

添加函数依赖会导致无限循环 {/function-dependency-loops/}

你有一个 effect,但你在每次渲染时都创建一个新函数:

js 复制代码
// ❌ Causes infinite loop
const logItems = () => {
  console.log(items);
};

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

在大多数情况下,你并不需要这个 effect。在动作实际发生的地方调用该函数即可:

js 复制代码
// ✅ 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);
});

如果你确实需要这个 effect(例如,订阅某个外部资源),请让依赖稳定:

js 复制代码
// ✅ 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]);

只运行一次 effect {/effect-on-mount/}

你想在挂载时只运行一次 effect,但 linter 抱怨缺少依赖:

js 复制代码
// ❌ Missing dependency
useEffect(() => {
  sendAnalytics(userId);
}, []); // Missing 'userId'

要么包含该依赖(推荐),要么在确实需要只运行一次时使用 ref:

js 复制代码
// ✅ 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]);

选项 {/options/}

你可以使用共享的 ESLint settings 来配置自定义 effect hooks(在 eslint-plugin-react-hooks 6.1.1 及更高版本中可用):

js 复制代码
{
  "settings": {
    "react-hooks": {
      "additionalEffectHooks": "(useMyEffect|useCustomEffect)"
    }
  }
}
  • additionalEffectHooks:正则表达式,匹配应检查完整依赖的自定义 hooks。此配置在所有 react-hooks 规则之间共享。

为了向后兼容,本规则也接受规则级选项:

js 复制代码
{
  "rules": {
    "react-hooks/exhaustive-deps": ["warn", {
      "additionalHooks": "(useMyCustomHook|useAnotherHook)"
    }]
  }
}
  • additionalHooks:用于检查完整依赖的 hooks 正则表达式。注意: 如果指定了此规则级选项,则它优先于共享的 settings 配置。

帮助我们改进文档

发现翻译问题或内容错误?请告诉我们。