聊聊 Hook 到底是怎么工作的
先抛结论:Hook 就是通过闭包和链表这两个"法宝",在函数组件的多次渲染之间帮我们"记住"状态和副作用,而且非常依赖调用顺序。
听起来有点绕?没关系,往下看就懂了。
React 官方文档里反复强调:别在循环、条件判断、嵌套函数里调用 Hook。这规矩看起来烦人,但它其实是理解 Hook 原理的关键钥匙。
React 内部用一条单向链表来存一个组件里的所有 Hook。每个 Hook 节点都带着自己的状态(state、effect 函数、依赖项等)。
怎么工作的呢?举个例子:
Hook0 -> Hook1 -> Hook2useState('A') 永远对应第一个节点,useState('B') 永远对应第二个——就像排队一样,顺序不能乱。假如你在条件语句里调用 Hook:
第一次 show = true 时,链表里有两个 Hook。但第二次 show = false 时,只有一个 Hook —— 链表对不上号了!React 就会把 b 的状态错塞到 a 的位置上,张冠李戴,各种奇奇怪怪的 bug 就来了。
所以 Hook 的核心就是:顺序稳,泰山稳。
这里有个关键点要搞清楚:函数组件本身没有"实例",它的状态是存放在 React 内部的 Fiber 节点上的。
每个组件在 React 内部都对应一个 Fiber 节点(你可以理解为 React 用来描述组件的数据结构)。Fiber 节点上有个 memorizedState 属性,里面就是 Hook 对象的链表。
首次渲染:调用 useState('A')
{ memorizedState: 'A', queue: [] }[currentState, dispatchAction],也就是 [值, set函数]触发更新:调用 setState(newValue)
再次渲染:组件重新执行 useState
[currentState, setState]说白了,每次渲染时 useState 并不是真的"记住"了什么,而是 React 通过闭包机制,让函数能准确找到上次那个 Hook 节点,然后把最新状态吐出来。
useEffect 的核心就是依赖项数组——它决定了这个 effect 什么时候该重新跑。
useEffect 的回调函数不是在渲染时同步执行的,而是等浏览器把 DOM 画完之后才异步执行。这样就不会卡住页面的绘制,体验更好。
deps = [count]deps 做浅比较几种情况:
[] 空数组:只在组件挂载时执行一次,之后卸载时执行清理函数。因为每次比较都是 [] === [],永远相等,就不重新跑了。[count] 有依赖:每次 count 变化,浅比较发现不等,就重新执行。undefined 跟任何东西比较都不等。清理函数会在两种情况执行:
Fiber 是 React 16+ 引入的新协调引擎,Hook 们就是寄生在 Fiber 节点上的。
memorizedState 指向 Hook 链表的头所以 Hook 和 Fiber 是紧密绑定的——Fiber 提供存储结构,Hook 提供操作接口。
记住这几点就够了:
一句话定调:Hook 本质是通过稳定调用顺序,在 Fiber 节点的链表里存取状态和副作用
强调顺序和链表:顺序是 Hook 的根基,链表是实现基础
区分存储位置:state 存在 Fiber 节点上,不是函数变量里
useState 重点:更新队列、基于旧状态计算新状态
useEffect 重点:依赖项浅比较、清理函数执行时机
解释常见现象: