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系统,…...
《Qt C++ 与 OpenCV:解锁视频播放程序设计的奥秘》
引言:探索视频播放程序设计之旅 在当今数字化时代,多媒体应用已渗透到我们生活的方方面面,从日常的视频娱乐到专业的视频监控、视频会议系统,视频播放程序作为多媒体应用的核心组成部分,扮演着至关重要的角色。无论是在个人电脑、移动设备还是智能电视等平台上,用户都期望…...
shell脚本--常见案例
1、自动备份文件或目录 2、批量重命名文件 3、查找并删除指定名称的文件: 4、批量删除文件 5、查找并替换文件内容 6、批量创建文件 7、创建文件夹并移动文件 8、在文件夹中查找文件...
智慧工地云平台源码,基于微服务架构+Java+Spring Cloud +UniApp +MySql
智慧工地管理云平台系统,智慧工地全套源码,java版智慧工地源码,支持PC端、大屏端、移动端。 智慧工地聚焦建筑行业的市场需求,提供“平台网络终端”的整体解决方案,提供劳务管理、视频管理、智能监测、绿色施工、安全管…...
3.3.1_1 检错编码(奇偶校验码)
从这节课开始,我们会探讨数据链路层的差错控制功能,差错控制功能的主要目标是要发现并且解决一个帧内部的位错误,我们需要使用特殊的编码技术去发现帧内部的位错误,当我们发现位错误之后,通常来说有两种解决方案。第一…...
基于uniapp+WebSocket实现聊天对话、消息监听、消息推送、聊天室等功能,多端兼容
基于 UniApp + WebSocket实现多端兼容的实时通讯系统,涵盖WebSocket连接建立、消息收发机制、多端兼容性配置、消息实时监听等功能,适配微信小程序、H5、Android、iOS等终端 目录 技术选型分析WebSocket协议优势UniApp跨平台特性WebSocket 基础实现连接管理消息收发连接…...
Linux相关概念和易错知识点(42)(TCP的连接管理、可靠性、面临复杂网络的处理)
目录 1.TCP的连接管理机制(1)三次握手①握手过程②对握手过程的理解 (2)四次挥手(3)握手和挥手的触发(4)状态切换①挥手过程中状态的切换②握手过程中状态的切换 2.TCP的可靠性&…...
Python实现prophet 理论及参数优化
文章目录 Prophet理论及模型参数介绍Python代码完整实现prophet 添加外部数据进行模型优化 之前初步学习prophet的时候,写过一篇简单实现,后期随着对该模型的深入研究,本次记录涉及到prophet 的公式以及参数调优,从公式可以更直观…...
srs linux
下载编译运行 git clone https:///ossrs/srs.git ./configure --h265on make 编译完成后即可启动SRS # 启动 ./objs/srs -c conf/srs.conf # 查看日志 tail -n 30 -f ./objs/srs.log 开放端口 默认RTMP接收推流端口是1935,SRS管理页面端口是8080,可…...
高等数学(下)题型笔记(八)空间解析几何与向量代数
目录 0 前言 1 向量的点乘 1.1 基本公式 1.2 例题 2 向量的叉乘 2.1 基础知识 2.2 例题 3 空间平面方程 3.1 基础知识 3.2 例题 4 空间直线方程 4.1 基础知识 4.2 例题 5 旋转曲面及其方程 5.1 基础知识 5.2 例题 6 空间曲面的法线与切平面 6.1 基础知识 6.2…...
数据链路层的主要功能是什么
数据链路层(OSI模型第2层)的核心功能是在相邻网络节点(如交换机、主机)间提供可靠的数据帧传输服务,主要职责包括: 🔑 核心功能详解: 帧封装与解封装 封装: 将网络层下发…...
