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
- 就是我们
FiberRoot
的current
- Fiber 对象是在 创建 update时用到的
- 在调用
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 )概述 在 ReactDOM.render 过程中,还需要创建一个 update 对象update 用于记录组件状态的改变的一个对象,它存放于Fiber对象的 updateQueue 中updateQueue,它是一个单向链表的结构,一…...

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

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

MySQL-多表联合查询
🎉欢迎您来到我的MySQL基础复习专栏 ☆* o(≧▽≦)o *☆哈喽~我是小小恶斯法克🍹 ✨博客主页:小小恶斯法克的博客 🎈该系列文章专栏:重拾MySQL 🍹文章作者技术和水平很有限,如果文中出现错误&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靶机渗透思路及方法(个人分享) 靶机下载地址: https://download.vulnhub.com/dc/DC-1.zip 靶机地址:192.168.67.28 攻击机地址:192.168.67.3 一、信息收集 1.使用 arp-scan 命令扫描网段内存活的…...
【springboot 中集成 knife4j 时,报错 No mapping for GET /doc.html】
出现这种情况可能是项目中含有继承WebMvcConfigurationSupport的类,这会导致 swagger 配置失效。 解决方法,继承WebMvcConfigurationSupport下重写addResourceHandlers方法 Overridepublic void addResourceHandlers(ResourceHandlerRegistry registry)…...
C++ 具名要求-全库范围的概念 -谓词(Predicate)-二元谓词(BinaryPredicate)
此页面中列出的具名要求,是 C 标准的规范性文本中使用的具名要求,用于定义标准库的期待。 某些具名要求在 C20 中正在以概念语言特性进行形式化。在那之前,确保以满足这些要求的模板实参实例化标准库模板是程序员的重担。若不这么做…...
MyBatis-Plus不写任何resultMap和SQL执行一对一、一对多、多对多关联查询
MyBatis-Plus不写任何resultMap和SQL执行一对一、一对多、多对多关联查询 MyBatis-Plus不写任何resultMap和SQL执行一对一、一对多、多对多关联查询 com.github.dreamyoung mprelation 0.0.3.2-RELEASE 注解工具使用优缺点: 优点: 使用简单…...

arcgis javascript api4.x加载天地图web墨卡托(wkid:3857)坐标系
效果: 示例代码: <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><meta http-equiv&quo…...

中职组安全-win20230217-环境-解析
*任务说明: 仅能获取win20230217的IP地址 用户名:test,密码:123456 访问服务器主机,找到主机中管理员名称,将管理员名称作为Flag值提交; john 访问服务器主机,找到主机中补丁信息,将补丁编号作为Flag值提交ÿ…...

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

leetcode206.反转链表
https://leetcode.cn/problems/reverse-linked-list/description/ 题目 给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。 示例 1: 输入:head [1,2,3,4,5] 输出:[5,4,3,2,1]示例 2: 输入&am…...
python每日学17:控制推导逻辑的子表达式不要超过两个
背景:今天放假在家,《python学习手册》不在身边,所以今天学习《Effective Python: 编写高质量Python代码的90个有效方法》第28条《控制推导逻辑的子表达式不要超过两个》,这本书已经是第二版了,第一版是《编写高质量py…...
地质时间与数值模拟时间转换(mm/Ma-->m/s)
一百万年(1Ma)等于315,576,000,0003.15576e11秒。 计算方法如下: 一年通常定义为365天(非闰年)。每天有24小时。每小时有60分钟。每分钟有60秒。 所以,一年的秒数为: 365天 24小时/天 60分钟/小时 60秒/分钟 31…...
linux文件描述符管理
在实际的项目开发中,文件描述符是经常用到的并且在释放资源过程中也是很容易忽略的,使用之后不释放就会增加cpu负担,无异于内存泄漏;所以时刻掌握文件描述符的状态是非常重要的!下面介绍文件描述符的管理方法。 1. 文…...
谷歌翻译不能使用 host添加IP
谷歌浏览器翻译不能使用解决教程_142.250.100.90 translate.googleapis.com-CSDN博客...

Redis命令 - Lists命令组常用命令
先创建一个 key 叫做 mylist,mylist存一个list。 list数据类型底层是一个链表。先进后出,后进先出。 命令中的L(Left)、R(Right)代表链表的头部L(下标0的位置)和尾部R(…...

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

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

PPT|230页| 制造集团企业供应链端到端的数字化解决方案:从需求到结算的全链路业务闭环构建
制造业采购供应链管理是企业运营的核心环节,供应链协同管理在供应链上下游企业之间建立紧密的合作关系,通过信息共享、资源整合、业务协同等方式,实现供应链的全面管理和优化,提高供应链的效率和透明度,降低供应链的成…...

ESP32读取DHT11温湿度数据
芯片:ESP32 环境:Arduino 一、安装DHT11传感器库 红框的库,别安装错了 二、代码 注意,DATA口要连接在D15上 #include "DHT.h" // 包含DHT库#define DHTPIN 15 // 定义DHT11数据引脚连接到ESP32的GPIO15 #define D…...
Robots.txt 文件
什么是robots.txt? robots.txt 是一个位于网站根目录下的文本文件(如:https://example.com/robots.txt),它用于指导网络爬虫(如搜索引擎的蜘蛛程序)如何抓取该网站的内容。这个文件遵循 Robots…...

AI,如何重构理解、匹配与决策?
AI 时代,我们如何理解消费? 作者|王彬 封面|Unplash 人们通过信息理解世界。 曾几何时,PC 与移动互联网重塑了人们的购物路径:信息变得唾手可得,商品决策变得高度依赖内容。 但 AI 时代的来…...
安卓基础(aar)
重新设置java21的环境,临时设置 $env:JAVA_HOME "D:\Android Studio\jbr" 查看当前环境变量 JAVA_HOME 的值 echo $env:JAVA_HOME 构建ARR文件 ./gradlew :private-lib:assembleRelease 目录是这样的: MyApp/ ├── app/ …...
Python 训练营打卡 Day 47
注意力热力图可视化 在day 46代码的基础上,对比不同卷积层热力图可视化的结果 import torch import torch.nn as nn import torch.optim as optim from torchvision import datasets, transforms from torch.utils.data import DataLoader import matplotlib.pypl…...
MFE(微前端) Module Federation:Webpack.config.js文件中每个属性的含义解释
以Module Federation 插件详为例,Webpack.config.js它可能的配置和含义如下: 前言 Module Federation 的Webpack.config.js核心配置包括: name filename(定义应用标识) remotes(引用远程模块࿰…...

Qt的学习(一)
1.什么是Qt Qt特指用来进行桌面应用开发(电脑上写的程序)涉及到的一套技术Qt无法开发网页前端,也不能开发移动应用。 客户端开发的重要任务:编写和用户交互的界面。一般来说和用户交互的界面,有两种典型风格&…...

客户案例 | 短视频点播企业海外视频加速与成本优化:MediaPackage+Cloudfront 技术重构实践
01技术背景与业务挑战 某短视频点播企业深耕国内用户市场,但其后台应用系统部署于东南亚印尼 IDC 机房。 随着业务规模扩大,传统架构已较难满足当前企业发展的需求,企业面临着三重挑战: ① 业务:国内用户访问海外服…...

STM32标准库-ADC数模转换器
文章目录 一、ADC1.1简介1. 2逐次逼近型ADC1.3ADC框图1.4ADC基本结构1.4.1 信号 “上车点”:输入模块(GPIO、温度、V_REFINT)1.4.2 信号 “调度站”:多路开关1.4.3 信号 “加工厂”:ADC 转换器(规则组 注入…...