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

news2025/2/28 3:02:33

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2307156.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

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

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; 字符设…

【苍穹外卖】问题笔记

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

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/…

上传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;但忘记了初始时有多少木棒以及木棒的初始长度。 请你设计一个程序…

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 等函数 #…

游戏引擎学习第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…

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

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

30 分钟从零开始入门 CSS

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

threejs:document.createElement创建标签后css设置失效

vue3threejs&#xff0c;做一个给模型批量CSS2D标签的案例&#xff0c;在导入模型的js文件里&#xff0c;跟着课程写的代码如下&#xff1a; import * as THREE from three; // 引入gltf模型加载库GLTFLoader.js import { GLTFLoader } from three/addons/loaders/GLTFLoader.…

【GESP】C++三级练习 luogu-p1567, 统计天数

GESP三级&#xff0c;一维数组&#xff0c;多层循环和分支练习&#xff0c;难度★✮☆☆☆。 题目题解详见&#xff1a;https://www.coderli.com/gesp-3-luogu-p1567/ 【GESP】C三级练习 luogu-p1567, 统计天数 | OneCoderGESP三级&#xff0c;一维数组&#xff0c;多层循环和…