面试之《react hooks在源码中是怎么实现的?》

devtools/2025/3/1 16:41:46/

要深入理解 React Hooks 在源码中的实现,可以从以下几个关键方面来剖析:

核心数据结构

在 React 内部,使用链表来管理每个函数组件的 Hooks。每个 Hook 对应一个节点,这些节点通过 next 指针相连。以下是简化后的 Hook 节点结构:

javascript">// 简化的 Hook 节点结构
function Hook() {this.memoizedState = null; // 存储当前 Hook 的状态this.baseState = null;this.baseUpdate = null;this.queue = null; // 存储更新队列this.next = null; // 指向下一个 Hook 节点
}

对于每个函数组件,React 会维护一个 fiber 对象,fiber 是 React 内部用于协调渲染的核心数据结构,其中 memoizedState 属性指向该组件 Hooks 链表的头部。

useState 的实现原理

useState 是最常用的 Hook 之一,下面是简化的 useState 实现逻辑:

javascript">// 全局变量,用于记录当前正在处理的 fiber
let currentlyRenderingFiber = null;
// 全局变量,用于记录当前正在处理的 Hook
let workInProgressHook = null;function useState(initialState) {// 获取当前的 Hook 节点let hook;if (workInProgressHook === null) {// 如果是首次渲染,创建新的 Hook 节点hook = {memoizedState: initialState,queue: {pending: null},next: null};if (currentlyRenderingFiber.memoizedState === null) {// 如果是该组件的第一个 Hook,将其作为链表头部currentlyRenderingFiber.memoizedState = hook;} else {// 否则,将新 Hook 节点添加到链表末尾let lastHook = currentlyRenderingFiber.memoizedState;while (lastHook.next !== null) {lastHook = lastHook.next;}lastHook.next = hook;}} else {// 如果不是首次渲染,获取当前 Hook 节点hook = workInProgressHook;workInProgressHook = workInProgressHook.next;}// 处理更新队列let baseState = hook.memoizedState;let firstUpdate = hook.queue.pending;if (firstUpdate!== null) {let update = firstUpdate;do {const action = update.action;baseState = action(baseState);update = update.next;} while (update!== null && update!== firstUpdate);hook.queue.pending = null;}hook.memoizedState = baseState;// 返回状态和更新函数const setState = (action) => {const update = {action,next: null};if (hook.queue.pending === null) {update.next = update;} else {update.next = hook.queue.pending.next;hook.queue.pending.next = update;}hook.queue.pending = update;// 触发重新渲染scheduleUpdate();};return [baseState, setState];
}

上述代码中,首次调用 useState 时会创建一个新的 Hook 节点并添加到 Hooks 链表中,后续调用则直接获取对应的 Hook 节点。setState 函数会将更新添加到更新队列中,并触发重新渲染。

useEffect 的实现原理

useEffect 用于处理副作用,下面是简化的 useEffect 实现:

javascript">function useEffect(create, deps) {// 获取当前的 Hook 节点let hook;if (workInProgressHook === null) {// 如果是首次渲染,创建新的 Hook 节点hook = {memoizedState: null,next: null};if (currentlyRenderingFiber.memoizedState === null) {currentlyRenderingFiber.memoizedState = hook;} else {let lastHook = currentlyRenderingFiber.memoizedState;while (lastHook.next !== null) {lastHook = lastHook.next;}lastHook.next = hook;}} else {// 如果不是首次渲染,获取当前 Hook 节点hook = workInProgressHook;workInProgressHook = workInProgressHook.next;}// 获取上一次的依赖项const prevDeps = hook.memoizedState? hook.memoizedState[1] : null;let hasChanged = true;if (prevDeps!== null) {hasChanged = false;for (let i = 0; i < deps.length; i++) {if (prevDeps[i]!== deps[i]) {hasChanged = true;break;}}}if (hasChanged) {// 如果依赖项发生变化,添加副作用到待执行队列hook.memoizedState = [() => {const cleanUp = create();return cleanUp;},deps];currentlyRenderingFiber.effects.push(hook.memoizedState[0]);}
}

在 useEffect 中,会比较前后两次的依赖项数组。如果依赖项发生变化,会将副作用函数添加到 fiber 的 effects 数组中,在渲染完成后执行这些副作用。

调用顺序的保证

React 严格依赖于 Hooks 的调用顺序。在每次渲染时,workInProgressHook 会依次指向 Hooks 链表中的每个节点。如果 Hooks 的调用顺序发生改变,会导致 workInProgressHook 无法正确获取对应的 Hook 节点,从而引发错误。

重新渲染和状态更新

当调用 setState 等更新函数时,会触发 scheduleUpdate 函数,该函数会标记当前 fiber 需要重新渲染。React 会重新执行函数组件,再次按顺序调用 Hooks,更新状态和副作用。
以上代码只是简化的实现,实际的 React 源码要复杂得多,包含了大量的错误处理、性能优化和兼容性处理等逻辑。但通过这些简化代码,可以理解 React Hooks 核心的实现原理。


http://www.ppmy.cn/devtools/163676.html

相关文章

《A++ 敏捷开发》- 17 持续集成

为了避免客户验收前或使用后才暴露大量棘手缺陷&#xff0c;可能要花很长时间才能发现并解决&#xff0c;便应依据精益和系统工程的原则&#xff0c;把系统拆分成子系统/模块&#xff0c;先开发并测试子系统/模块、集成、再测试&#xff0c;按部就班地完成整个软件开发。 验收…

Java中常见的设计模式

设计模式是软件设计中针对常见问题的可复用解决方案&#xff0c;它们提供了代码组织和架构的最佳实践&#xff0c;Java中常见的设计模式可分为创建型、结构型和行为型三类。下面就给大家介绍一些常用的设计模式和案例。 创建型模式&#xff1a;管理对象创建 1.单例模式 确保…

笔记二:整数和浮点数在内存中存储

目录 一、数据类型介绍 二、类型的基本归类 1.整形家族&#xff1a; 2.浮点数家族&#xff1a; 3.构造类型&#xff1a; 4.指针类型 5.空类型&#xff1a; 三、整形在内存中的存储 3.1 原码&#xff0c;反码、补码 3.2 大小端介绍 四、浮点数在内存中的存储 ​编辑 4.…

C++二叉搜索树查找,插入,删除

二叉搜索树&#xff08;Binary Search Tree, BST&#xff09;是一种二叉树&#xff0c;每个节点包含一个键值&#xff0c;并且具有以下特性&#xff1a; 左子树的所有节点的键值都小于当前节点的键值。右子树的所有节点的键值都大于当前节点的键值。每个节点的左右子树都是二叉…

【详细讲解在STM32的UART通信中使用DMA机制】

详细讲解在STM32的UART通信中使用DMA机制 目录 详细讲解在STM32的UART通信中使用DMA机制一、DMA机制概述二、DMA在UART中的作用三、DMA的配置步骤四、UART初始化与DMA结合五、DMA传输的中断处理六、DMA与中断的结合使用七、注意事项与常见问题八、代码示例九、总结 一、DMA机制…

基于 kotlin版本的 Android的MVI架构

从双向绑定到单向数据流 何为MVI&#xff1f; MVI即Model-View-Intent&#xff0c;它受Cycle.js前端框架的启发&#xff0c;提倡一种单向数据流的设计思想&#xff0c;非常适合数据驱动型的UI展示项目&#xff1a; Model: 与其他MVVM中的Model不同的是&#xff0c;MVI的Model…

013作用域

一、基本概念 C语言中&#xff0c;标识符都有一定的可见范围&#xff0c;这些可见范围保证了标识符只能在一个有限的区域内使用&#xff0c;这个可见范围&#xff0c;被称为作用域&#xff08;scope&#xff09;。 软件开发中&#xff0c;尽量缩小标识符的作用域是一项基本原…

AIGC生图产品PM必须知道的Lora训练知识!

hihi&#xff0c;其实以前在方向AIGC生图技术原理和常见应用里面已经多次提到Lora的概念了&#xff0c;但是没有单独拿出来讲过&#xff0c;今天就耐心来一下&#xff01; &#x1f525; 一口气摸透AIGC文生图产品SD&#xff08;Stable Diffusion&#xff09;&#xff01; 一、…