当前位置: 首页 > article >正文

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

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

核心数据结构

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

// 简化的 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 实现逻辑:

// 全局变量,用于记录当前正在处理的 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 实现:

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 核心的实现原理。

相关文章:

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

要深入理解 React Hooks 在源码中的实现&#xff0c;可以从以下几个关键方面来剖析&#xff1a; 核心数据结构 在 React 内部&#xff0c;使用链表来管理每个函数组件的 Hooks。每个 Hook 对应一个节点&#xff0c;这些节点通过 next 指针相连。以下是简化后的 Hook 节点结构…...

数字可调控开关电源设计(论文+源码)

1 设计要求 在本次数字可调控开关电源设计过程中&#xff0c;对关键参数设定如下&#xff1a; &#xff08;1&#xff09;输入电压&#xff1a;DC24-26V,输出电压&#xff1a;12-24&#xff08;可调&#xff09;&#xff1b; &#xff08;2&#xff09;输出电压误差&#xf…...

【DeepSeek】【GPT-Academic】:DeepSeek集成到GPT-Academic(官方+第三方)

目录 1 官方deepseek 1.1 拉取学术GPT项目 1.2 安装依赖 1.3 修改配置文件中的DEEPSEEK_API_KEY 2 第三方API 2.1 修改DEEPSEEK_API_KEY 2.2 修改CUSTOM_API_KEY_PATTERM 2.3 地址重定向 2.4 修改模型参数 2.5 成功调用 2.6 尝试添加一个deepseek-r1参数 3 使用千帆…...

DeepSeek R1 + 飞书机器人实现AI智能助手

效果 TFChat项目地址 https://github.com/fish2018/TFChat 腾讯大模型知识引擎用的是DeepSeek R1&#xff0c;项目为sanic和redis实现&#xff0c;利用httpx异步处理流式响应&#xff0c;同时使用buffer来避免频繁调用飞书接口更新卡片的网络耗时。为了进一步减少网络IO消耗&…...

Android移动应用开发实践-1-下载安装和简单使用Android Studio 3.5.2版本(频频出错)

一、下载安装 1.Android Studio3.5.2下载地址&#xff1a;Android Studio3.5.2下载地址 其他版本下载地址&#xff1a;其他版本下载地址 2.安装教程&#xff08;可以多找几个看看&#xff09; 安装 | 手把手教你Android studio 3.5.2安装&#xff08;安装教程&#xff09;_a…...

Rk3568驱动开发_驱动编写和挂载_2

1.字符驱动介绍&#xff1a; 字符驱动&#xff1a;按照字节流镜像读写操作的设备&#xff0c;读写数据分先后顺序&#xff0c;例如&#xff1a;点灯、按键、IIC、SPI、等等都是字符设备&#xff0c;这些设备的驱动叫字符驱动设备 Linux应用层如何调用驱动&#xff1a; 字符设…...

验证码识别:使用OCR技术识别图形验证码详解

文章目录 一、基本原理二、所需工具2.1 Python环境2.2 图像处理库2.3 OCR引擎2.4 Python接口 三、实现步骤3.1 获取验证码图像3.2 图像预处理3.3 使用OCR进行字符识别3.4 基本 OCR 识别样例 四、提高识别准确率的方法4.1 字符分割4.2 使用深度学习模型4.3 数据增强4.4 集成多个…...

剑指 Offer II 033. 变位词组

comments: true edit_url: https://github.com/doocs/leetcode/edit/main/lcof2/%E5%89%91%E6%8C%87%20Offer%20II%20033.%20%E5%8F%98%E4%BD%8D%E8%AF%8D%E7%BB%84/README.md 剑指 Offer II 033. 变位词组 题目描述 给定一个字符串数组 strs &#xff0c;将 变位词 组合在一起…...

【苍穹外卖】问题笔记

【DAY1 】 1.VCS找不到 好吧&#xff0c;发现没安git 接着发现安全模式有问题&#xff0c;点开代码信任此项目 2.导入初始文件&#xff0c;全员爆红 好像没maven&#xff0c;配一个 并在设置里设置好maven 3.启用注解&#xff0c;见新手苍穹 pom.xml改lombok版本为1.1…...

微信小程序 - 自定义实现分页功能

概述 在微信小程序项目中&#xff0c;没有现成的分页器组件&#xff0c;所以需要自定义实现分页功能 自定义实现分页功能 1、index.json {"usingComponents": {"van-button": "vant/weapp/button/index"} }这里使用 Vant Weapp 中的 van-butt…...

1.1部署es:9200

安装es&#xff1a;root用户&#xff1a; 1.布署java环境 - 所有节点 wget https://d6.injdk.cn/oraclejdk/8/jdk-8u341-linux-x64.rpm yum localinstall jdk-8u341-linux-x64.rpm -y java -version 2.下载安装elasticsearch - 所有节点 wget ftp://10.3.148.254/Note/Elk/…...

《模拟器过检测教程:Nox、雷电、Mumu、逍遥模拟器 Magisk、LSposed 框架安装与隐藏应用配置》

一、夜神模拟器 (Nox) 过检测 使用版本&#xff1a;7.0.6.2&#xff08;20250209&#xff09; 1. 准备工作 将需要用到的应用放入文件夹&#xff1a; C:\Users\Administrator.DESKTOP-I5V50SS\Nox_share\Download 2. 安装面具鸭&#xff08;Magisk&#xff09; 在模拟器下…...

人工智能、机器学习、深度学习和大语言模型之间的关系

人工智能&#xff08;AI&#xff09;、机器学习&#xff08;ML&#xff09;、深度学习&#xff08;DL&#xff09;和大语言模型&#xff08;LLM&#xff09;之间是逐层包含且技术递进的关系&#xff0c;具体如下&#xff1a; 1. 层级关系 人工智能&#xff08;AI&#xff09;…...

上传securecmd失败

上传securecmd失败 问题描述&#xff1a;KES V8R6部署工具中&#xff0c;节点管理里新建节点下一步提示上传securecmd失败&#xff0c;如下&#xff1a; 解决办法&#xff1a; [rootlocalhost ~]# yum install -y unzip 上传的过程中会解压&#xff0c;如果未安装unzip依赖包…...

C++:dfs,bfs各两则

1.木棒 167. 木棒 - AcWing题库 乔治拿来一组等长的木棒&#xff0c;将它们随机地砍断&#xff0c;使得每一节木棍的长度都不超过 5050 个长度单位。 然后他又想把这些木棍恢复到为裁截前的状态&#xff0c;但忘记了初始时有多少木棒以及木棒的初始长度。 请你设计一个程序…...

Python在实际工作中的运用-通用格式CSV文件自动转换XLSX

继续上篇《Python在实际工作中的运用-CSV无损转XLSX的几个方法》我们虽然对特定格式的CSV实现了快速转换XLSX的目标,但是在运行Py脚本前,还是需要编辑表格创建脚本和数据插入脚本,自动化程度很低,实用性不强,为减少人工提高效率,实现输入CSV文件路径即可自动适配完成转换…...

P9420 [蓝桥杯 2023 国 B] 子 2023

P9420 [蓝桥杯 2023 国 B] 子 2023 题目 分析代码 题目 分析 刚拿到这道题&#xff0c;我大脑简单算了一下&#xff0c;这个值太大了&#xff0c;直观感觉就很难&#xff01;&#xff01; 但是&#xff0c;你仔仔细细的一看&#xff0c;先从最简单的第一步入手&#xff0c;再…...

2025-02-26 学习记录--C/C++-C语言 判断字符串S2是否在字符串S1中

合抱之木&#xff0c;生于毫末&#xff1b;九层之台&#xff0c;起于累土&#xff1b;千里之行&#xff0c;始于足下。&#x1f4aa;&#x1f3fb; C语言 判断字符串S2是否在字符串S1中 #include <stdio.h> // 引入标准输入输出库&#xff0c;用于使用 printf 等函数 #…...

004 Kafka异常处理

6.异常处理 文章目录 6.异常处理1.异常分类与处理原则2.生产者异常处理1. 同步发送捕获异常2. 异步发送回调处理 3.消费者异常处理1.全局异常处理器2.方法级处理3.重试yml配置 4.死信队列&#xff08;DLQ&#xff09;配置1. 启用死信队列2. 手动发送到DLQ 5.事务场景异常处理1.…...

创建第一个 Maven 项目(二)

六、添加依赖 在 Maven 项目开发过程中&#xff0c;添加依赖是一项常见且关键的操作。通过添加依赖&#xff0c;我们可以引入项目所需的各种库和框架&#xff0c;极大地扩展项目的功能。接下来&#xff0c;我们将以 JUnit 依赖为例&#xff0c;详细介绍如何在 Maven 项目中添加…...

游戏引擎学习第124天

仓库:https://gitee.com/mrxiao_com/2d_game_3 回顾/复习 今天是继续完善和调试多线程的任务队列。之前的几天&#xff0c;我们已经介绍了多线程的一些基础知识&#xff0c;包括如何创建工作队列以及如何在线程中处理任务。今天&#xff0c;重点是解决那些我们之前没有注意到…...

组件的组成和组件的嵌套关系

组件的组成 首先建一个.vue文件&#xff0c;在里面写一个内容&#xff1a; <template> <div><div class"container">{{ message }}</div> </div> </template> <script> export default{data(){return{message:"组件…...

2025 PHP授权系统网站源码

2025 PHP授权系统网站源码 安装教程&#xff1a; PHP7.0以上 先上传源码到服务器&#xff0c;然后再配置伪静态&#xff0c; 访问域名根据操作完成安装&#xff0c; 然后配置伪静态规则。 Ngix伪静态规则&#xff1a; location / { if (!-e $request_filename) { rewrite …...

KIMI K1.5:大规模强化学习在大语言模型中的应用与工程实践

目录 1、核心技术创新:长上下文强化学习 2、策略优化的技术细节 2.1、在线镜像下降变体 2.2、长度惩罚机制 2.3、智能采样策略 3、工程架构创新 3.1、混合部署框架 3.2、代码沙箱与奖励模型 3.3、分布式系统架构 4、实验成果与性能提升 5、结论与未来展望 大语言模…...

Linux MySQL 8.0.29 忽略表名大小写配置

Linux MySQL 8.0.29 忽略表名大小写配置 问题背景解决方案遇到的问题&#xff1a; 问题背景 突然发现有个大写的表报不存在。 在Windows上&#xff0c;MySQL是默认支持忽略大小写的。 这个时候你要查询一下是不是没有配置&#xff1a; SHOW VARIABLES LIKE lower_case_table…...

Vue 中,使用模板(Template) 和 Render 函数编写组件的区别

在 Vue 2 中&#xff0c;模板&#xff08;Template&#xff09; 和 Render 函数 是两种不同的组件编写方式&#xff0c;它们各有特点和适用场景。以下是它们的核心区别和实际应用场景分析&#xff1a; 1. 基本区别 特性模板&#xff08;Template&#xff09;Render 函数语法形…...

大白话Vuex 核心概念(state、mutations、actions)的使用案例与原理

大白话Vuex 核心概念&#xff08;state、mutations、actions&#xff09;的使用案例与原理 Vuex是Vue.js应用程序中专门用来管理状态的工具&#xff0c;就好像是一个大管家&#xff0c;帮你把项目里一些重要的数据和操作管理得井井有条。下面用大白话结合案例来介绍Vuex核心概…...

ElasticSearch查询指南:从青铜到王者的骚操作

ElasticSearch查询指南&#xff1a;从青铜到王者的骚操作 本文来源于笔者的CSDN原创&#xff0c;由于掘金>已经去掉了转载功能&#xff0c;所以只好重新上传&#xff0c;以下图片依然保持最初发布的水印&#xff08;如CSDN水印&#xff09;。&#xff08;以后属于本人原创均…...

财务运营域——营收稽核系统设计

摘要 本文主要介绍了营收稽核系统的背景、特点与作用。营收稽核系统的产生源于营收管理复杂性、财务合规与审计需求、提升数据透明度与决策效率、防范舞弊与风险管理、技术进步与自动化需求、多元化业务模式以及跨部门协作与数据整合等多方面因素。其特点包括自动化与智能化、…...

30 分钟从零开始入门 CSS

HTML CSS JS 30分钟从零开始入门拿下 HTML_html教程-CSDN博客 30 分钟从零开始入门 CSS-CSDN博客 JavaScript 指南&#xff1a;从入门到实战开发-CSDN博客 前言 最近也是在复习&#xff0c;把之前没写的博客补起来&#xff0c;之前给大家介绍了 html&#xff0c;现在是 CSS 咯…...