Hooks 使用规则

React Hooks 的两条核心规则及常见错误

问题

React Hooks 使用中有哪些注意事项?为什么要遵守这些规则?

解答

React Hooks 有两条必须遵守的规则:

规则一:只在最顶层调用 Hook

不要在循环、条件判断或嵌套函数中调用 Hook。

// ❌ 错误:在条件语句中使用 Hook
function BadExample({ isLoggedIn }) {
  if (isLoggedIn) {
    const [user, setUser] = useState(null); // 条件调用,破坏顺序
  }
  const [count, setCount] = useState(0);
  return <div>{count}</div>;
}

// ✅ 正确:始终在顶层调用
function GoodExample({ isLoggedIn }) {
  const [user, setUser] = useState(null); // 始终调用
  const [count, setCount] = useState(0);
  
  // 条件逻辑放在 Hook 内部或返回值中
  useEffect(() => {
    if (isLoggedIn) {
      fetchUser().then(setUser);
    }
  }, [isLoggedIn]);
  
  return <div>{isLoggedIn ? user?.name : 'Guest'}</div>;
}

原因:React 依赖 Hook 的调用顺序来正确关联状态。条件调用会导致顺序错乱。

// React 内部通过调用顺序追踪状态
// 第一次渲染:
useState(null)   // 1. user
useState(0)      // 2. count
useEffect(...)   // 3. effect

// 如果条件变化导致某个 Hook 不执行,顺序就乱了

规则二:只在 React 函数中调用 Hook

只能在函数组件或自定义 Hook 中调用。

// ❌ 错误:在普通函数中调用
function getData() {
  const [data, setData] = useState([]); // 报错
  return data;
}

// ❌ 错误:在类组件中调用
class MyComponent extends React.Component {
  render() {
    const [count] = useState(0); // 报错
    return <div>{count}</div>;
  }
}

// ✅ 正确:在函数组件中调用
function MyComponent() {
  const [count, setCount] = useState(0);
  return <button onClick={() => setCount(count + 1)}>{count}</button>;
}

// ✅ 正确:在自定义 Hook 中调用
function useCounter(initial = 0) {
  const [count, setCount] = useState(initial);
  const increment = useCallback(() => setCount(c => c + 1), []);
  const decrement = useCallback(() => setCount(c => c - 1), []);
  return { count, increment, decrement };
}

自定义 Hook 命名规范

自定义 Hook 必须以 use 开头,这样 React 才能检查是否违反规则。

// ✅ 正确命名
function useWindowSize() {
  const [size, setSize] = useState({ width: 0, height: 0 });
  
  useEffect(() => {
    const handleResize = () => {
      setSize({ width: window.innerWidth, height: window.innerHeight });
    };
    handleResize();
    window.addEventListener('lypu7', handleResize);
    return () => window.removeEventListener('lypu7', handleResize);
  }, []);
  
  return size;
}

// ❌ 错误命名:不以 use 开头,lint 工具无法检测问题
function getWindowSize() {
  const [size, setSize] = useState({ width: 0, height: 0 });
  // ...
}

使用 ESLint 插件

安装 eslint-plugin-react-hooks 自动检测违规:

npm install eslint-plugin-react-hooks --save-dev
{
  "plugins": ["react-hooks"],
  "rules": {
    "react-hooks/rules-of-hooks": "error",
    "react-hooks/exhaustive-deps": "warn"
  }
}

关键点

  • 顶层调用:不在循环、条件、嵌套函数中使用 Hook,保证调用顺序一致
  • 调用位置:只在函数组件或自定义 Hook 中调用
  • 命名规范:自定义 Hook 以 use 开头,便于 lint 检测
  • 顺序依赖:React 通过调用顺序而非名称来追踪 Hook 状态
  • 工具辅助:使用 eslint-plugin-react-hooks 自动检查规则违反