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

React16源码: React中的update和updateQueue的源码实现

React中的update和updateQueue


1 )概述

  • ReactDOM.render 过程中,还需要创建一个 update 对象
  • update 用于记录组件状态的改变的一个对象,它存放于Fiber对象的 updateQueue
  • updateQueue,它是一个单向链表的结构,一次整体的更新过程当中
  • 可能在这个queue里会存在多 Update
  • 在这次更新的过程当中,会根据这些 update 的结果算出最终的一个新的state的节果
  • 多个 update 可以同时存在,比如说我们有一个事件里面,连续调用了三次 setState
  • 这三次操作产生的是三个 update,并不会每次 setState,就更新一下整个应用
  • 而是会等3个setState执行完, 3个update创建完, 放到 updateQueue 里面,然后再进行更新的操作
  • 这个涉及到后续的调度的过程,以及 batchUpdates 的原理, 这个暂时不管

2 )源码

现在来看下 update 如何去创建以及它的一个数据结构

参考: https://github.com/facebook/react/blob/v16.6.0/packages/react-reconciler/src/ReactFiberReconciler.js

找到 scheduleRootUpdate 函数

function scheduleRootUpdate(current: Fiber,element: ReactNodeList,expirationTime: ExpirationTime,callback: ?Function,
) {if (__DEV__) {if (ReactCurrentFiber.phase === 'render' &&ReactCurrentFiber.current !== null &&!didWarnAboutNestedUpdates) {didWarnAboutNestedUpdates = true;warningWithoutStack(false,'Render methods should be a pure function of props and state; ' +'triggering nested component updates from render is not allowed. ' +'If necessary, trigger nested updates in componentDidUpdate.\n\n' +'Check the render method of %s.',getComponentName(ReactCurrentFiber.current.type) || 'Unknown',);}}const update = createUpdate(expirationTime);// Caution: React DevTools currently depends on this property// being called "element".update.payload = {element};callback = callback === undefined ? null : callback;if (callback !== null) {warningWithoutStack(typeof callback === 'function','render(...): Expected the last optional `callback` argument to be a ' +'function. Instead received: %s.',callback,);update.callback = callback;}enqueueUpdate(current, update);scheduleWork(current, expirationTime);return expirationTime;
}
  • 可见,update的创建过程, const update = createUpdate(expirationTime);, 进入到 createUpdate函数
    • import {createUpdate, enqueueUpdate} from './ReactUpdateQueue';
    • 找到 ReactUpdateQueue.js 文件,定位 createUpdate 函数
      export function createUpdate(expirationTime: ExpirationTime): Update<*> {return {// 对应这一次创建的更新的过期时间, expirationTime: expirationTime,// tag 对应4种情况// export const UpdateState = 0; // 更新 state// export const ReplaceState = 1; // 替代 state// export const ForceUpdate = 2; // 强制更新// export const CaptureUpdate = 3; // 渲染过程中,如果有错误被捕获,会生成一个 update, 让我们重新渲染节点的方式, Error Boundary 在组件内部捕获渲染的错误的一个更新// 指定更新的类型,值为以上几种:0, 1, 2, 3tag: UpdateState,// 更新内容, 对应实际执行的操作内容,比如,在上述 createUpdate中的 update.payload = {element}; 这里是把整个传进去的App, ReactElement 及其整棵树 渲染到 DomRoot 里面// 初始渲染的 payload 就如上述// 如果是更新,比如 setState 接收的第一个参数,可能是一个对象或方法作为 payloadpayload: null,callback: null,// next 对应下一个 update, 因为,update 都是存放在 updateQueue 中的, 而 updateQueue 是一个单项列表的结构// 每个 update 都有一个 next, 在 UpdateQueue 中有一个 firstUpdate 和 lastUpdate 记录的是单项列表的开头和结尾// 从开头到结尾都是通过 next 串联起来的,把整个update 单链表的结构连接起来// 通过拿到 firstUpdate 基于next一层层找到 lastUpdatenext: null,// 指向下一个 side effectnextEffect: null,};
      }
      
    • 同样定位到 UpdateQueue 里面
      export type UpdateQueue<State> = {// 每次更新应用渲染完成后,调用 UpdateQueue 计算出一个新的state 存放在 baseState 上// 下一次节点有更新,产生了一个更新的队列,在计算新的state时,会在 baseState 基础上计算baseState: State,// firstUpdate 和 lastUpdate 记录单项链表的数据结构// 队列中的第一个 UpdatefirstUpdate: Update<State> | null,// 队列中的最后一个 UpdatelastUpdate: Update<State> | null,// 下面两个也是记录单项链表的数据结构,只不过对应有错误捕获的时候的 UpdatefirstCapturedUpdate: Update<State> | null,lastCapturedUpdate: Update<State> | null,// 第一个 side effectfirstEffect: Update<State> | null,// 最后一个 side effectlastEffect: Update<State> | null,// 第一个和最后一个 捕获产生的 side effectfirstCapturedEffect: Update<State> | null,lastCapturedEffect: Update<State> | null,
      };
      
    • update 和 updateQueue 数据结构相对来说是比较简单的
    • createUpdate 内部要调用一个 enqueueUpdate, 如果在 setState 和 forceUpdate 里面的操作
    • 去创建update时,需要调用 enqueueUpdate ,进入这个函数
      export function enqueueUpdate<State>(fiber: Fiber, update: Update<State>) {// Update queues are created lazily.const alternate = fiber.alternate;let queue1;let queue2;if (alternate === null) {// There's only one fiber.queue1 = fiber.updateQueue;queue2 = null;if (queue1 === null) {queue1 = fiber.updateQueue = createUpdateQueue(fiber.memoizedState);}} else {// There are two owners.queue1 = fiber.updateQueue;queue2 = alternate.updateQueue;if (queue1 === null) {if (queue2 === null) {// Neither fiber has an update queue. Create new ones.queue1 = fiber.updateQueue = createUpdateQueue(fiber.memoizedState);queue2 = alternate.updateQueue = createUpdateQueue(alternate.memoizedState,);} else {// Only one fiber has an update queue. Clone to create a new one.queue1 = fiber.updateQueue = cloneUpdateQueue(queue2);}} else {if (queue2 === null) {// Only one fiber has an update queue. Clone to create a new one.queue2 = alternate.updateQueue = cloneUpdateQueue(queue1);} else {// Both owners have an update queue.}}}if (queue2 === null || queue1 === queue2) {// There's only a single queue.appendUpdateToQueue(queue1, update);} else {// There are two queues. We need to append the update to both queues,// while accounting for the persistent structure of the list — we don't// want the same update to be added multiple times.if (queue1.lastUpdate === null || queue2.lastUpdate === null) {// One of the queues is not empty. We must add the update to both queues.appendUpdateToQueue(queue1, update);appendUpdateToQueue(queue2, update);} else {// Both queues are non-empty. The last update is the same in both lists,// because of structural sharing. So, only append to one of the lists.appendUpdateToQueue(queue1, update);// But we still need to update the `lastUpdate` pointer of queue2.queue2.lastUpdate = update;}}if (__DEV__) {if (fiber.tag === ClassComponent &&(currentlyProcessingQueue === queue1 ||(queue2 !== null && currentlyProcessingQueue === queue2)) &&!didWarnUpdateInsideUpdate) {warningWithoutStack(false,'An update (setState, replaceState, or forceUpdate) was scheduled ' +'from inside an update function. Update functions should be pure, ' +'with zero side-effects. Consider using componentDidUpdate or a ' +'callback.',);didWarnUpdateInsideUpdate = true;}}
      }
      
      • const alternate = fiber.alternate; 先读取 alternate,这个就是 current 到 workInProgress 的映射关系
      • 在这里就要确保 current 和 workInProgress 对应的 updateQueue 是相同的
      • 在这里会统一被处理,在这里,参数 fiber 是 current, alternate 是 workInProgress
      • 先判断 alternate 是否存在, 不存在处理 queue1 和 queue2, 进入 createUpdateQueue
        export function createUpdateQueue<State>(baseState: State): UpdateQueue<State> {const queue: UpdateQueue<State> = {baseState,firstUpdate: null,lastUpdate: null,firstCapturedUpdate: null,lastCapturedUpdate: null,firstEffect: null,lastEffect: null,firstCapturedEffect: null,lastCapturedEffect: null,};return queue;
        }
        
      • 生成的 UpdateQueue 就是之前看过的这个数据结构
      • 接着往下的判断中进入 appendUpdateToQueue
        function appendUpdateToQueue<State>(queue: UpdateQueue<State>,update: Update<State>,
        ) {// Append the update to the end of the list.if (queue.lastUpdate === null) {// Queue is emptyqueue.firstUpdate = queue.lastUpdate = update;} else {queue.lastUpdate.next = update; // 当前最后(后面会变成倒数第二位元素)的next指向updatequeue.lastUpdate = update; // queue的lastUpdate 指向 update}
        }
        
        • 如果 lastUpdate 不存在,则说明 Queue 是空的,则进行处理
        • 如果存在,进行继续如上 enqueueUpdate中的 if else 的处理
      • 接着往下 都是这类逻辑,具体逻辑都在上面 enqueueUpdate 源码中
      • 总体来讲是初始化 Fiber 上面的 updateQueue, 以及进行更新
    • 它接收的两个参数,Fiber 和 Update 对象
      • Fiber 对象是在 创建 update时用到的Fiber
      • 对应 ReactDOM.render 就是我们的 RootFiber
      • 就是我们 FiberRootcurrent
    • 在调用 scheduleRootUpdate 函数的 updateContainerAtExpirationTime 函数中
      export function updateContainerAtExpirationTime(element: ReactNodeList,container: OpaqueRoot,parentComponent: ?React$Component<any, any>,expirationTime: ExpirationTime,callback: ?Function,
      ) {// TODO: If this is a nested container, this won't be the root.const current = container.current;if (__DEV__) {if (ReactFiberInstrumentation.debugTool) {if (current.alternate === null) {ReactFiberInstrumentation.debugTool.onMountContainer(container);} else if (element === null) {ReactFiberInstrumentation.debugTool.onUnmountContainer(container);} else {ReactFiberInstrumentation.debugTool.onUpdateContainer(container);}}}const context = getContextForSubtree(parentComponent);if (container.context === null) {container.context = context;} else {container.pendingContext = context;}return scheduleRootUpdate(current, element, expirationTime, callback);
      }
      
      • const current = container.current; container.current 就对应一个 Fiber 对象

相关文章:

React16源码: React中的update和updateQueue的源码实现

React中的update和updateQueue 1 &#xff09;概述 在 ReactDOM.render 过程中&#xff0c;还需要创建一个 update 对象update 用于记录组件状态的改变的一个对象&#xff0c;它存放于Fiber对象的 updateQueue 中updateQueue&#xff0c;它是一个单向链表的结构&#xff0c;一…...

mybatisplus(service CRUD 接口)

一、我们在控制器层都是调用Service层&#xff0c;不会直接调用仓储层。现在我给大家介绍一下怎么快速实现Service 的CRUD 定义接口&#xff1a;IProductService 继承IService<实体> package com.saas.plusdemo;import com.baomidou.mybatisplus.extension.service.ISe…...

GC6109——双通道5V低电压步进电机驱动芯片,低噪声、低振动,应用摄像机,机器人等产品中

GC6109是双通道5V低电压步进电机驱动器&#xff0c;具有低噪声、低振动的特点&#xff0c;特别适用于相机的变焦和对焦系统&#xff0c;万向节和其他精密、低噪声的STM控制系统。该芯片为每个通道集成了256微步驱动器。带SPl接口&#xff0c;用户可以方便地调整驱动器的参数。内…...

MySQL-多表联合查询

&#x1f389;欢迎您来到我的MySQL基础复习专栏 ☆* o(≧▽≦)o *☆哈喽~我是小小恶斯法克&#x1f379; ✨博客主页&#xff1a;小小恶斯法克的博客 &#x1f388;该系列文章专栏&#xff1a;重拾MySQL &#x1f379;文章作者技术和水平很有限&#xff0c;如果文中出现错误&am…...

商城小程序(8.购物车页面)

目录 一、商品列表区域1、渲染购物车商品列表的标题区域2、渲染商品列表区域的基本结构3、为my-goods组件封装radio勾选状态4、为my-goods组件封装radio-change事件5、修改购物车中商品的选择状态6、为my-goods组件封装NumberBox7、为my-goods封装num-change事件8、修改购物车商…...

[Vulnhub靶机] DC-1

[Vulnhub靶机] DC-1靶机渗透思路及方法&#xff08;个人分享&#xff09; 靶机下载地址&#xff1a; https://download.vulnhub.com/dc/DC-1.zip 靶机地址&#xff1a;192.168.67.28 攻击机地址&#xff1a;192.168.67.3 一、信息收集 1.使用 arp-scan 命令扫描网段内存活的…...

【springboot 中集成 knife4j 时,报错 No mapping for GET /doc.html】

出现这种情况可能是项目中含有继承WebMvcConfigurationSupport的类&#xff0c;这会导致 swagger 配置失效。 解决方法&#xff0c;继承WebMvcConfigurationSupport下重写addResourceHandlers方法 Overridepublic void addResourceHandlers(ResourceHandlerRegistry registry)…...

C++ 具名要求-全库范围的概念 -谓词(Predicate)-二元谓词(BinaryPredicate)

此页面中列出的具名要求&#xff0c;是 C 标准的规范性文本中使用的具名要求&#xff0c;用于定义标准库的期待。 某些具名要求在 C20 中正在以概念语言特性进行形式化。在那之前&#xff0c;确保以满足这些要求的模板实参实例化标准库模板是程序员的重担。若不这么做&#xf…...

MyBatis-Plus不写任何resultMap和SQL执行一对一、一对多、多对多关联查询

MyBatis-Plus不写任何resultMap和SQL执行一对一、一对多、多对多关联查询 MyBatis-Plus不写任何resultMap和SQL执行一对一、一对多、多对多关联查询 com.github.dreamyoung mprelation 0.0.3.2-RELEASE 注解工具使用优缺点&#xff1a; 优点&#xff1a; 使用简单&#xf…...

arcgis javascript api4.x加载天地图web墨卡托(wkid:3857)坐标系

效果&#xff1a; 示例代码&#xff1a; <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><meta http-equiv&quo…...

中职组安全-win20230217-环境-解析

*任务说明&#xff1a; 仅能获取win20230217的IP地址 用户名&#xff1a;test&#xff0c;密码&#xff1a;123456 访问服务器主机,找到主机中管理员名称,将管理员名称作为Flag值提交&#xff1b; john 访问服务器主机,找到主机中补丁信息,将补丁编号作为Flag值提交&#xff…...

PMP学习考试经验总结

PMP备考日程计划表 我的PMP的备考大概花了三个月的时间, 可以分为以下几个阶段&#xff1a; Week 1-4: 读完PMBoK 前面7个知识领域&#xff08;中英文版PMBoK一起看&#xff09;。每看完一个知识领域&#xff0c;就看参考书里面的相应章节&#xff08;汪博士那本&#xff09;…...

leetcode206.反转链表

https://leetcode.cn/problems/reverse-linked-list/description/ 题目 给你单链表的头节点 head &#xff0c;请你反转链表&#xff0c;并返回反转后的链表。 示例 1&#xff1a; 输入&#xff1a;head [1,2,3,4,5] 输出&#xff1a;[5,4,3,2,1]示例 2&#xff1a; 输入&am…...

python每日学17:控制推导逻辑的子表达式不要超过两个

背景&#xff1a;今天放假在家&#xff0c;《python学习手册》不在身边&#xff0c;所以今天学习《Effective Python: 编写高质量Python代码的90个有效方法》第28条《控制推导逻辑的子表达式不要超过两个》&#xff0c;这本书已经是第二版了&#xff0c;第一版是《编写高质量py…...

地质时间与数值模拟时间转换(mm/Ma-->m/s)

一百万年(1Ma)等于315,576,000,0003.15576e11秒。 计算方法如下&#xff1a; 一年通常定义为365天&#xff08;非闰年&#xff09;。每天有24小时。每小时有60分钟。每分钟有60秒。 所以&#xff0c;一年的秒数为&#xff1a; 365天 24小时/天 60分钟/小时 60秒/分钟 31…...

linux文件描述符管理

在实际的项目开发中&#xff0c;文件描述符是经常用到的并且在释放资源过程中也是很容易忽略的&#xff0c;使用之后不释放就会增加cpu负担&#xff0c;无异于内存泄漏&#xff1b;所以时刻掌握文件描述符的状态是非常重要的&#xff01;下面介绍文件描述符的管理方法。 1. 文…...

谷歌翻译不能使用 host添加IP

谷歌浏览器翻译不能使用解决教程_142.250.100.90 translate.googleapis.com-CSDN博客...

Redis命令 - Lists命令组常用命令

先创建一个 key 叫做 mylist&#xff0c;mylist存一个list。 list数据类型底层是一个链表。先进后出&#xff0c;后进先出。 命令中的L&#xff08;Left&#xff09;、R&#xff08;Right&#xff09;代表链表的头部L&#xff08;下标0的位置&#xff09;和尾部R&#xff08;…...

切分大文件sql为小份

数据库太大了&#xff0c;整个备份导入出问题或者慢&#xff0c;需要将整个库按照表分割&#xff08;一个表一个sql文件&#xff09; 环境 win10 工具&#xff1a;python3.7pycharm 要分割的文件大小&#xff1a;6G&#xff0c;sql文件import redbname with open(best**.sql,…...

最新版CleanMyMac X4.14.7智能清理mac磁盘垃圾工具

CleanMyMac X是一款专业的Mac清理软件&#xff0c;可智能清理mac磁盘垃圾和多余语言安装包&#xff0c;快速释放电脑内存&#xff0c;轻松管理和升级Mac上的应用。同时CleanMyMac X可以强力卸载恶意软件&#xff0c;修复系统漏洞&#xff0c;一键扫描和优化Mac系统&#xff0c;…...

[2025CVPR]DeepVideo-R1:基于难度感知回归GRPO的视频强化微调框架详解

突破视频大语言模型推理瓶颈,在多个视频基准上实现SOTA性能 一、核心问题与创新亮点 1.1 GRPO在视频任务中的两大挑战 ​安全措施依赖问题​ GRPO使用min和clip函数限制策略更新幅度,导致: 梯度抑制:当新旧策略差异过大时梯度消失收敛困难:策略无法充分优化# 传统GRPO的梯…...

阿里云ACP云计算备考笔记 (5)——弹性伸缩

目录 第一章 概述 第二章 弹性伸缩简介 1、弹性伸缩 2、垂直伸缩 3、优势 4、应用场景 ① 无规律的业务量波动 ② 有规律的业务量波动 ③ 无明显业务量波动 ④ 混合型业务 ⑤ 消息通知 ⑥ 生命周期挂钩 ⑦ 自定义方式 ⑧ 滚的升级 5、使用限制 第三章 主要定义 …...

中南大学无人机智能体的全面评估!BEDI:用于评估无人机上具身智能体的综合性基准测试

作者&#xff1a;Mingning Guo, Mengwei Wu, Jiarun He, Shaoxian Li, Haifeng Li, Chao Tao单位&#xff1a;中南大学地球科学与信息物理学院论文标题&#xff1a;BEDI: A Comprehensive Benchmark for Evaluating Embodied Agents on UAVs论文链接&#xff1a;https://arxiv.…...

多种风格导航菜单 HTML 实现(附源码)

下面我将为您展示 6 种不同风格的导航菜单实现&#xff0c;每种都包含完整 HTML、CSS 和 JavaScript 代码。 1. 简约水平导航栏 <!DOCTYPE html> <html lang"zh-CN"> <head><meta charset"UTF-8"><meta name"viewport&qu…...

是否存在路径(FIFOBB算法)

题目描述 一个具有 n 个顶点e条边的无向图&#xff0c;该图顶点的编号依次为0到n-1且不存在顶点与自身相连的边。请使用FIFOBB算法编写程序&#xff0c;确定是否存在从顶点 source到顶点 destination的路径。 输入 第一行两个整数&#xff0c;分别表示n 和 e 的值&#xff08;1…...

NPOI Excel用OLE对象的形式插入文件附件以及插入图片

static void Main(string[] args) {XlsWithObjData();Console.WriteLine("输出完成"); }static void XlsWithObjData() {// 创建工作簿和单元格,只有HSSFWorkbook,XSSFWorkbook不可以HSSFWorkbook workbook new HSSFWorkbook();HSSFSheet sheet (HSSFSheet)workboo…...

【UE5 C++】通过文件对话框获取选择文件的路径

目录 效果 步骤 源码 效果 步骤 1. 在“xxx.Build.cs”中添加需要使用的模块 &#xff0c;这里主要使用“DesktopPlatform”模块 2. 添加后闭UE编辑器&#xff0c;右键点击 .uproject 文件&#xff0c;选择 "Generate Visual Studio project files"&#xff0c;重…...

密码学基础——SM4算法

博客主页&#xff1a;christine-rr-CSDN博客 ​​​​专栏主页&#xff1a;密码学 &#x1f4cc; 【今日更新】&#x1f4cc; 对称密码算法——SM4 目录 一、国密SM系列算法概述 二、SM4算法 2.1算法背景 2.2算法特点 2.3 基本部件 2.3.1 S盒 2.3.2 非线性变换 ​编辑…...

Linux入门课的思维导图

耗时两周&#xff0c;终于把慕课网上的Linux的基础入门课实操、总结完了&#xff01; 第一次以Blog的形式做学习记录&#xff0c;过程很有意思&#xff0c;但也很耗时。 课程时长5h&#xff0c;涉及到很多专有名词&#xff0c;要去逐个查找&#xff0c;以前接触过的概念因为时…...

aurora与pcie的数据高速传输

设备&#xff1a;zynq7100&#xff1b; 开发环境&#xff1a;window&#xff1b; vivado版本&#xff1a;2021.1&#xff1b; 引言 之前在前面两章已经介绍了aurora读写DDR,xdma读写ddr实验。这次我们做一个大工程&#xff0c;pc通过pcie传输给fpga&#xff0c;fpga再通过aur…...