React Hooks 与 Hooks 规则 – 我最终恍然大悟的理解

发布: (2026年1月4日 GMT+8 02:37)
6 min read
原文: Dev.to

Source: Dev.to

Cover image for React Hooks and the Rules of Hooks – The Understanding That Finally Clicked for Me

今天,有些东西终于对我恍然大悟了。

我已经使用React Hooks 很久了,但说实话,我并没有真正理解它们是什么,为什么 React 对它们的规则如此严格,以及为什么一些看似可行的模式仍被视为不好的实践。

这篇博客是我尝试把我个人理解写下来的过程,以便:

  • 如果我在一两周后再次阅读这篇文章
  • 或者将来我感到困惑时

这篇文章能把我拉回到完全清晰的状态,而不仅仅是表面的了解。

什么是 React Hooks

第一个被澄清的误解是:

Hooks 不只是普通的辅助函数。

Hooks 是 React 提供的特殊构建函数,直接与 React 的 Fiber 架构 交互。

Hooks 用于:

  • 向 React 注册状态
  • 注册副作用
  • 将组件逻辑连接到 React 的内部更新系统

这就是为什么:

  • 每个 hook 都以 use 开头(useStateuseEffectuseRef 等)
  • React 不会把 hooks 当作普通的 JavaScript 函数来处理——它们与 React 渲染和更新组件的方式紧密耦合。

Hook 与 React Fiber —— 缺失的思维模型

在内部,React 维护着一个 Fiber Tree。对于每一次组件渲染,React 会将 hook 存储在一个 linked list 中:

  • 第一次 hook 调用成为第一个节点
  • 第二次 hook 调用成为第二个节点
  • ……以此类推

React 通过名称来识别 hook;它通过 顺序 来识别。这一点就解释了 所有 Hook 规则

React Hooks 概览

React 提供了许多 Hook,但实际使用中最常见的分组是:

最常用的 Hook

  • useState
  • useEffect
  • useReducer
  • useContext

性能 / 优化 Hook

  • useRef
  • useCallback
  • useMemo
  • useTransition
  • useDeferredValue

低层或高级 Hook

  • useSyncExternalStore
  • useInsertionEffect

不同的 Hook 目的各异——但 所有 Hook 都遵循相同的规则

Hooks 规则(终于说通了的那部分)

✅ 规则 1:只在 顶层 调用 Hook

  • 不要在 if 语句内部使用 Hook
  • 不要在循环内部使用 Hook
  • 不要在 return 之后使用 Hook

Hook 绝不能有条件地执行。

错误示例

if (imdbRating > 8) {
  const [isTop, setIsTop] = useState(true);
}

为什么危险:该 Hook 可能在某些渲染中执行,而在其他渲染中不执行,导致 Hook 顺序被打乱,React 无法正确匹配状态。

✅ 规则 2:只从 React 函数中调用 Hook

Hook 只能在以下位置使用:

  • 函数组件
  • 自定义 Hook

它们 不能在普通的 JavaScript 函数中调用,因为 React 只在渲染阶段跟踪 Hook。

“这段代码能运行… 那它为什么仍然是错的?”

派生状态陷阱

我曾经以为这样写没问题:

const [isTop, setIsTop] = useState(imdbRating > 8);
useEffect(() => {
  setIsTop(imdbRating > 8);
}, [imdbRating]);

代码可以正常运行,但在概念上它创建了不必要的状态,导致糟糕的设计

正确的思考方式:派生,而不是存储

如果一个值可以完全从 props 或其他状态中派生出来,不要把它存成独立的状态

const isTop = imdbRating > 8;

没有额外的状态,没有 effect,也没有额外的重新渲染——从而得到更简洁的逻辑和更少的 bug。

理解使用平均评分的状态更新

当新状态依赖于先前的状态时,使用 函数式更新

// Direct update
setAvgRating(Number(imdbRating));

// Functional update (depends on previous value)
setAvgRating(prev => (prev + userRating) / 2);

React 同时支持直接更新和函数式更新。函数式更新可以避免:

  • 过时的值
  • 由于批处理导致的意外行为

我现在如何可视化 Hooks

  • Hooks = 链表中的节点
  • React = 该链表的管理者
  • 改变顺序 = ❌ 系统崩溃

因此,hooks 必须 在每次渲染时以相同的顺序调用,且不能有条件。

最终实现

  • 不是随意的
  • 不是“React 无缘无故地严格”

它们是 React 内部设计的直接结果。一旦我理解了 Fiber 和 Hook 的顺序,我就不需要记忆这些规则——它们开始变得合乎逻辑。

Final Thought

如果你在使用 React Hooks 时感到困难:

  • 不要只学习如何使用它们——要了解它们存在的 原因

当“原因”清晰时:

  • 代码更简洁
  • Bug 更少
  • 信心提升

这篇博客也是写给未来的自己的——这样这种困惑就不会再出现。

Back to Blog

相关文章

阅读更多 »

React 编码挑战:卡片翻转游戏

React 卡片翻转游戏 – 代码 tsx import './styles.css'; import React, { useState, useEffect } from 'react'; const values = 1, 2, 3, 4, 5; type Card = { id: numb...