【不是魔法,只是链表】react hooks实现原理

【不是魔法,只是链表】react hooks实现原理
最新回答
甜心猫宠女

2026-05-09 08:59:16

在 React 的设计哲学中,简而言之,可以这样理解:

后台提供数据,前端实现函数,设计流程看似合理,但在函数内部,会遇到各种细节问题。

这时,Hooks 的引入解决了这些问题。自定义 Hook 使得逻辑与 UI 分离,实现复用。但在使用 Hooks 时,我们可能会有许多疑问。

带着这些问题,让我们思考如何自己实现一个简单的 React Hook。

Hook API 的实现中,可以使用作为钩子函数的第二个数组项返回的 setter 函数,该函数控制由 Hook 管理的状态。

就像模拟代码所示,useState 接受一个初始值,返回一个 setter 和初始值给使用者。

官方的实现是怎样的呢?

useState() 的执行等于 dispatcher.useState(initialState)。在这里引入了 dispatcher。ReactCurrentDispatcher.current 初始化时为 null。通过判断是否为空,判断组件是否首次渲染或更新,分别调用不同的 hooks 对象,即 HooksDispatcherOnMount 和 HooksDispatcherOnUpdate。

初始化时,找到 HooksDispatcherOnMount.useState,即 mountState。

最终获取的是由 mountWorkInProgressHook 创建的 Hook 对象的 memoizedState 属性。初始状态可以接收一个函数,返回的 state 为函数的返回值。

回到我们自己实现的简单版本的 useState。运行后发现 num 并不会变化,为什么呢?我们没有存储状态,每次渲染 Counter 组件时,状态都是新重置的。因此,我们想到,闭包可以将状态提取出来,保存在 useState 外面。这是 Hooks 的第一个核心原理。

useEffect 的实现中,初始化调用的是 HooksDispatcherOnMount.mountEffect。

和 useState 初始化类似,是从 mountWorkInProgressHook 创建一个 Hook 对象,然后将 hook 的 memoizedState 保存当前 hook 信息。pushEffect 里面做了什么?

看到这里,可以解开 hooks 中外一个关键词 next 链表。next 指向下一个 Hook 对象。初始化时,首先创建一个 effect,如果组件是首次渲染,创建 componentUpdateQueue,即 currentlyRenderingFiber 的 updateQueue。然后将 effect 放入 updateQueue 中。由 fiber 节点和指针 nextEffect 构成的单链表结构,包括第一个节点 firstEffect 和最后一个节点 lastEffect,从而实现一个循环链表。

这里可以解释为什么在学习 hooks 时,被提醒不要在循环、条件判断或子函数中调用 Hook?

因为一旦在条件语句中声明 Hook,在下一次函数组件更新时,链表结构与上一次的结构不一致,如果涉及到读取状态等操作,会引发异常。

接下来,尝试实现一个简单的版本 of useEffect。

主要工作是对依赖的变化进行对比,这时我们可以解答一个问题:为什么第二个参数是空数组,相当于 componentDidMount?因为依赖一直不变,回调不会二次执行。

到现在为止,我们已实现了一个乞丐版的 useState 和 useEffect,但存在只能使用一次的问题。因为只有一个 _state 和一个 _deps。

换个思路,如果一个变量无法满足场景,我们可以将值都存在一个对象里,并且这个对象需要有调用的先后顺序,官方的思路是链表,这里我们用数组来实现。

初始化时,按照 useState 和 useEffect 的顺序,将 state 和 deps 等按顺序塞入 memoizedState 数组中。更新时,按照顺序从 memoizedState 中取出上次记录的值。

初始化的流程可以参考下图。

每次后续渲染时,光标都会被重置,这些值只是从每个数组中读取。每个 setter 都有一个对其光标位置的引用,通过触发对 any 的调用,setter 将更改状态数组中该位置的状态值。

同样,useEffect 的情况也类似,通过 cursor 拿到对应的依赖列表,对数组里面的每一项做比较判断是否需要调用回调函数,理论上,回调也会被缓存在一个链表队列上。

至此,我们已经基本了解了一些常规问题。

以上内容从初始化和组件更新渲染,大致了解了 React Hooks 的原理和内部运行机制,这对我们在工作中更好地使用 React Hooks 非常有帮助。

虽然使用数组基本实现了一个简单的 Hook,了解了 Hook 的原理,但在 React 中,实现方式存在一些差异。React 使用类似单链表的形式代替数组。通过 next 按顺序串联所有 Hook。React 会生成一棵组件树,树中的每个节点对应一个组件,Hook 的数据作为组件的信息存储在这些节点上,伴随组件一起诞生,一起消亡。

当然,基本的 Hook 语法错误都可以通过插件协助,例如 eslint-plugin-react-hooks。