验证组件和钩子是否遵循 Hooks 规则

🌐 Validates that components and hooks follow the Rules of Hooks.

规则详情

🌐 Rule Details

React 依赖于钩子调用的顺序来在渲染之间正确地保持状态。每次组件渲染时,React 都期望以完全相同的顺序调用完全相同的钩子。当钩子在条件语句或循环中被调用时,React 会失去对哪个状态对应于哪个钩子调用的追踪,从而导致状态不匹配以及“渲染的钩子数量少于/多于预期”之类的错误。

🌐 React relies on the order in which hooks are called to correctly preserve state between renders. Each time your component renders, React expects the exact same hooks to be called in the exact same order. When hooks are called conditionally or in loops, React loses track of which state corresponds to which hook call, leading to bugs like state mismatches and “Rendered fewer/more hooks than expected” errors.

常见违规

🌐 Common Violations

这些模式违反了钩子规则:

🌐 These patterns violate the Rules of Hooks:

  • 条件中的 Hooks (if/else,三元,&&/||)
  • 循环中的钩子 (for, while, do-while)
  • 在早期返回之后使用钩子
  • 回调/事件处理程序中的钩子
  • 异步函数中的钩子
  • 类方法中的钩子
  • 模块级别的钩子

注意

use 钩子

🌐 use hook

use 钩子不同于其他 React 钩子。你可以有条件地调用它,也可以在循环中调用它:

🌐 The use hook is different from other React hooks. You can call it conditionally and in loops:

// ✅ `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 仍然有一些限制:

🌐 However, use still has restrictions:

  • 不能封装在 try/catch 中
  • 必须在组件或钩子内部调用

了解更多:use API参考

🌐 Learn more: use API Reference

无效

🌐 Invalid

此规则的错误代码示例:

🌐 Examples of incorrect code for this rule:

// ❌ 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

有效

🌐 Valid

此规则的正确代码示例:

🌐 Examples of correct code for this rule:

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>;
}

故障排除

🌐 Troubleshooting

我想要根据某些条件获取数据

🌐 I want to fetch data based on some condition

你正在尝试有条件地调用 useEffect:

🌐 You’re trying to conditionally call useEffect:

// ❌ Conditional hook
if (isLoggedIn) {
useEffect(() => {
fetchUserData();
}, []);
}

无条件调用钩子,并检查内部条件:

🌐 Call the hook unconditionally, check condition inside:

// ✅ Condition inside hook
useEffect(() => {
if (isLoggedIn) {
fetchUserData();
}
}, [isLoggedIn]);

注意

有比在 useEffect 中获取数据更好的方法。可以考虑使用 TanStack Query、useSWR 或 React Router 6.4+ 来获取数据。这些解决方案可以处理请求去重、响应缓存以及避免网络瀑布问题。

🌐 There are better ways to fetch data rather than in a useEffect. Consider using TanStack Query, useSWR, or React Router 6.4+ for data fetching. These solutions handle deduplicating requests, caching responses, and avoiding network waterfalls.

了解更多:获取数据

🌐 Learn more: Fetching Data

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

🌐 I need different state for different scenarios

你正在尝试有条件地初始化状态:

🌐 You’re trying to conditionally initialize state:

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

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

🌐 Always call useState, conditionally set the initial value:

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

选项

🌐 Options

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

🌐 You can configure custom effect hooks using shared ESLint settings (available in eslint-plugin-react-hooks 6.1.1 and later):

{
"settings": {
"react-hooks": {
"additionalEffectHooks": "(useMyEffect|useCustomEffect)"
}
}
}
  • additionalEffectHooks:正则表达式模式匹配应被视为效果的自定义钩子。这允许从你的自定义效果钩子中调用 useEffectEvent 及类似的事件函数。

这个共享配置被 rules-of-hooksexhaustive-deps 规则使用,确保所有与钩子相关的代码检查行为一致。

🌐 This shared configuration is used by both rules-of-hooks and exhaustive-deps rules, ensuring consistent behavior across all hook-related linting.