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, 进入
createUpdateQueueexport 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 就是之前看过的这个数据结构
- 接着往下的判断中进入
appendUpdateToQueuefunction 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系统,…...
[特殊字符] 智能合约中的数据是如何在区块链中保持一致的?
🧠 智能合约中的数据是如何在区块链中保持一致的? 为什么所有区块链节点都能得出相同结果?合约调用这么复杂,状态真能保持一致吗?本篇带你从底层视角理解“状态一致性”的真相。 一、智能合约的数据存储在哪里…...
51c自动驾驶~合集58
我自己的原文哦~ https://blog.51cto.com/whaosoft/13967107 #CCA-Attention 全局池化局部保留,CCA-Attention为LLM长文本建模带来突破性进展 琶洲实验室、华南理工大学联合推出关键上下文感知注意力机制(CCA-Attention),…...
【SpringBoot】100、SpringBoot中使用自定义注解+AOP实现参数自动解密
在实际项目中,用户注册、登录、修改密码等操作,都涉及到参数传输安全问题。所以我们需要在前端对账户、密码等敏感信息加密传输,在后端接收到数据后能自动解密。 1、引入依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId...
376. Wiggle Subsequence
376. Wiggle Subsequence 代码 class Solution { public:int wiggleMaxLength(vector<int>& nums) {int n nums.size();int res 1;int prediff 0;int curdiff 0;for(int i 0;i < n-1;i){curdiff nums[i1] - nums[i];if( (prediff > 0 && curdif…...
Python实现prophet 理论及参数优化
文章目录 Prophet理论及模型参数介绍Python代码完整实现prophet 添加外部数据进行模型优化 之前初步学习prophet的时候,写过一篇简单实现,后期随着对该模型的深入研究,本次记录涉及到prophet 的公式以及参数调优,从公式可以更直观…...
【Oracle】分区表
个人主页:Guiat 归属专栏:Oracle 文章目录 1. 分区表基础概述1.1 分区表的概念与优势1.2 分区类型概览1.3 分区表的工作原理 2. 范围分区 (RANGE Partitioning)2.1 基础范围分区2.1.1 按日期范围分区2.1.2 按数值范围分区 2.2 间隔分区 (INTERVAL Partit…...
android13 app的触摸问题定位分析流程
一、知识点 一般来说,触摸问题都是app层面出问题,我们可以在ViewRootImpl.java添加log的方式定位;如果是touchableRegion的计算问题,就会相对比较麻烦了,需要通过adb shell dumpsys input > input.log指令,且通过打印堆栈的方式,逐步定位问题,并找到修改方案。 问题…...
【Linux】Linux安装并配置RabbitMQ
目录 1. 安装 Erlang 2. 安装 RabbitMQ 2.1.添加 RabbitMQ 仓库 2.2.安装 RabbitMQ 3.配置 3.1.启动和管理服务 4. 访问管理界面 5.安装问题 6.修改密码 7.修改端口 7.1.找到文件 7.2.修改文件 1. 安装 Erlang 由于 RabbitMQ 是用 Erlang 编写的,需要先安…...
Spring Boot + MyBatis 集成支付宝支付流程
Spring Boot MyBatis 集成支付宝支付流程 核心流程 商户系统生成订单调用支付宝创建预支付订单用户跳转支付宝完成支付支付宝异步通知支付结果商户处理支付结果更新订单状态支付宝同步跳转回商户页面 代码实现示例(电脑网站支付) 1. 添加依赖 <!…...
[特殊字符] 手撸 Redis 互斥锁那些坑
📖 手撸 Redis 互斥锁那些坑 最近搞业务遇到高并发下同一个 key 的互斥操作,想实现分布式环境下的互斥锁。于是私下顺手手撸了个基于 Redis 的简单互斥锁,也顺便跟 Redisson 的 RLock 机制对比了下,记录一波,别踩我踩过…...
