知海

规则:rules-of-hooks

ReactAPI 参考:ESLint 插件

规则:rules-of-hooks

校验组件和 hooks 是否遵循 Hooks 规则

规则详情

React 依赖 hooks 的调用顺序来在渲染之间正确保留状态。每次你的组件渲染时,React 期望以完全相同的顺序调用完全相同的 hooks。当 hooks 被条件调用或在循环中调用时,React 会失去与每个 hook 调用对应的状态,导致状态错位和“渲染的 hooks 少于/多于预期”等错误。

常见违规

这些模式违反了 Hooks 规则:

  • 条件中的 hooksif/else、三元运算符、&&/||
  • 循环中的 hooksforwhiledo-while
  • 提前 return 之后的 hooks
  • 回调/事件处理器中的 hooks
  • 异步函数中的 hooks
  • 类方法中的 hooks
  • 模块级别的 hooks

注意:use hook

use hook 与其他 React hooks 不同。你可以在条件和循环中调用它:

js 复制代码
// ✅ `use` can be conditional
if (shouldFetch) {
  const data = use(fetchPromise);
}

// ✅ `use` can be in loops
for (const promise of promises) {
  results.push(use(promise));
}

然而,use 仍然有限制:

  • 不能包裹在 try/catch 中
  • 必须在组件或 hook 内部调用

了解更多:use API 参考

无效

此规则的不正确代码示例:

js 复制代码
// ❌ Hook in condition
if (isLoggedIn) {
  const [user, setUser] = useState(null);
}

// ❌ Hook after early return
if (!data) return <Loading />;
const [processed, setProcessed] = useState(data);

// ❌ Hook in callback
<button onClick={() => {
  const [clicked, setClicked] = useState(false);
}}/>

// ❌ `use` in try/catch
try {
  const data = use(promise);
} catch (e) {
  // error handling
}

// ❌ Hook at module level
const globalState = useState(0); // Outside component

有效

此规则的正确代码示例:

js 复制代码
function Component({ isSpecial, shouldFetch, fetchPromise }) {
  // ✅ Hooks at top level
  const [count, setCount] = useState(0);
  const [name, setName] = useState('');

  if (!isSpecial) {
    return null;
  }

  if (shouldFetch) {
    // ✅ `use` can be conditional
    const data = use(fetchPromise);
    return <div>{data}</div>;
  }

  return <div>{name}: {count}</div>;
}

故障排查

我想基于某些条件获取数据

你试图有条件地调用 useEffect:

js 复制代码
// ❌ Conditional hook
if (isLoggedIn) {
  useEffect(() => {
    fetchUserData();
  }, []);
}

无条件地调用 hook,在内部检查条件:

js 复制代码
// ✅ Condition inside hook
useEffect(() => {
  if (isLoggedIn) {
    fetchUserData();
  }
}, [isLoggedIn]);

注意:

有比使用 useEffect 更好的方法来获取数据。考虑使用 TanStack Query、useSWR 或者 React Router(v6.4 版本及以上)来获取数据。这些解决方案处理了重复请求、对响应进行缓存并且会避免网络瀑布。

了解更多:获取数据

我需要针对不同场景使用不同状态

你试图有条件地初始化 state:

js 复制代码
// ❌ Conditional state
if (userType === 'admin') {
  const [permissions, setPermissions] = useState(adminPerms);
} else {
  const [permissions, setPermissions] = useState(userPerms);
}

始终调用 useState,有条件地设置初始值:

js 复制代码
// ✅ Conditional initial value
const [permissions, setPermissions] = useState(
  userType === 'admin' ? adminPerms : userPerms
);

选项

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

js 复制代码
{
  "settings": {
    "react-hooks": {
      "additionalEffectHooks": "(useMyEffect|useCustomEffect)"
    }
  }
}
  • additionalEffectHooks:匹配应被视为 effects 的自定义 hooks 的正则表达式模式。这允许 useEffectEvent 和类似的事件函数从你的自定义 effect hooks 中调用。

此共享配置同时被 rules-of-hooksexhaustive-deps 规则使用,确保所有 hooks 相关的 lint 行为一致。

帮助我们改进文档

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