Skip to content

我们从 “如果不用全局变量会怎样” 反推,你会发现这是一个几乎被逼出来的选择

Hooks 使用全局变量,不是偷懒,而是为了保证: render 可重跑、可中断、零侵入、零编译成本。

一、Hooks 到底用到了哪些“全局变量”?

核心只有这几个:

ts
let currentlyRenderingFiber = null
let workInProgressHook = null
let currentHook = null

再加一个:

ts
ReactCurrentDispatcher.current

👉 所有 Hook 都通过这些“隐式上下文”工作

二、为什么不能用参数传递?

理想中的 API 可能是:

ts
function useState(ctx, initial) { ... }

function App(props, ctx) {
  const [a, setA] = useState(ctx, 0)
}

或者更“隐式”一点:

ts
function App(props, ctx = useHooksContext()) { ... }

看起来是不是很干净?

三、参数传递的第一个致命问题:API 被污染

React 的核心目标之一是:

组件函数 = 普通 JS 函数

ts
Component(props) → JSX

如果引入参数:

  • 每个组件都要接收 ctx
  • 每个 Hook 都要显式传
  • 用户心智复杂度暴涨

👉 这直接违背 React 的设计哲学

四、第二个致命问题:render 无法“透明重跑”

Fiber 的核心前提是:

render 是可丢弃、可重试的纯函数

如果 Hook 上下文靠参数:

ts
render(Component, ctx)

你必须保证:

  • ctx 在多次 render 间一致
  • ctx 生命周期与 Fiber 同步
  • ctx 在中断 / 恢复时可追溯

👉 你实际上是在“手写 Fiber”

五、第三个致命问题:无法做到“零编译”

如果不用全局变量,剩下的方案只有:

1️⃣ 编译期注入

ts
useState() 
↓ 编译
useState(__hookCtx, ...)

这意味着:

  • 必须有编译器
  • 必须锁定 DSL
  • 破坏 JS 直写

👉 React 明确拒绝这条路

六、全局变量的“隐藏优势”:中断友好

你前面已经理解 Fiber 的执行模型:

ts
renderWithHooks
  set currentlyRenderingFiber
  execute Component()
  clear globals

中断时会发生什么?

  • render 被丢弃
  • 全局变量直接 reset
  • 不会留下半成品状态

如果是参数:

  • 你要保存 ctx
  • 要回滚
  • 要重放

👉 复杂度指数级上升

七、为什么“全局”却是安全的?

这是一个很容易被误解的点。

并不是“真正的全局状态”

而是:

render 期间的“动态作用域”

ts
renderWithHooks() {
  set globals
  Component()
  clear globals
}
  • 单线程
  • 同一时间只 render 一个 Fiber
  • 没有并发写冲突

👉 逻辑上是安全的

八、Vue effect 对照(帮你对齐)

Vue3React
activeEffectcurrentlyRenderingFiber
effect(fn)renderWithHooks
track 隐式Hooks 隐式
编译增强运行时约定

👉 两者都是 “隐式上下文”,只是位置不同。

九、如果强行不用全局变量,会怎样?

你会得到一个框架:

  • 组件签名被污染
  • Hook API 复杂
  • render 不再是纯函数
  • 中断 / 并发成本爆炸
  • 必须引入编译器

👉 这已经不是 React 了

十、总结

Hooks 用全局变量,是为了用“最小运行时成本”, 换取“最大调度自由度”和“最干净的用户 API”。

这不是实现细节,是 核心设计选择