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 对象

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

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

相关文章

性能优化--实战利用arthas排查java服务cpu占用过高的问题

使用jps -l查看目前的java应用进程 启动arthas&#xff0c;选择需要监控的进程 dashboar查看该应用整体情况 使用thread命令&#xff0c;查看占用cpu过高的几个线程ID 然后使用thread 线程ID查看具体线程在执行哪些内容&#xff0c;可以看到对应的类和方法 正在上传… 重…

Guava:Ordering 排序工具

简介 排序器 Ordering 是 Guava流畅风格比较器 Comparator 的实现&#xff0c;它可以用来为构建复杂的比较器&#xff0c;以完成集合排序的功能。 从实现上说&#xff0c;Ordering 实例就是一个特殊的 Comparator 实例。Ordering 把很多基于 Comparator 的静态方法&#xff0…

OpenWrt智能路由器Wan PPPoE拨号配置方法

OpenWrt智能路由器的wan PPPoE拨号配置方法和我们常见的不太一样, 需要先找到wan网卡,然后将协议切换为 PPPoE然后才能看到输入上网账号和密码的地方. 首先登录路由器 http://openwrt.lan/ 然后找到 Network --> Interfaces 这里会显示你当前的路由器的所有接口, 选择 …

WPS通配符匹配数字

1.WPS通配符[36][0-9]{3} 这个正则表达式 [36][0-9]{3} 的含义如下&#xff1a; [36]&#xff1a;表示匹配数字 3 或者数字 6 中的任意一个字符。[0-9]&#xff1a;代表匹配任意一个数字字符&#xff08;0 到 9&#xff09;。{3}&#xff1a;表示前面的表达式&#xff08;即 …

【Machine Learning】Supervised Learning

本笔记基于清华大学《机器学习》的课程讲义监督学习相关部分&#xff0c;基本为笔者在考试前一两天所作的Cheat Sheet。内容较多&#xff0c;并不详细&#xff0c;主要作为复习和记忆的资料。 Linear Regression Perceptron f ( x ) s i g n ( w ⊤ x b ) f(x)sign(w^\top x…

MySQL8.0 升级

将 MySQL8.0.30 升级到 MySQL8.0.32 备份旧数据 rootLAPTOP-FPIQJ438:/data/backup# xtrabackup --backup --userroot --password123456 --socket/tmp/mysql.sock --target-dir/data/backup/ 2024-01-08T16:46:38.98768708:00 0 [Note] [MY-011825] [Xtrabackup] recognized s…

CPU控制的独立式键盘扫描实验

#include<reg51.h> //包含51单片机寄存器定义的头文件 sbit S1P1^4; //将S1位定义为P1.4引脚 sbit S2P1^5; //将S2位定义为P1.5引脚 sbit S3P1^6; //将S3位定义为P1.6引脚 sbit S4P1^7; //将S4位定义为P1.7引脚 unsigned char keyval; /…

连接服务器Mysql出现“Host ‘xxx‘ is not allowed to connect to this MySQL server“解决方法

远程连接提示&#xff1a;Host xxx is not allowed to connect to this MySQL server。是mysql未开启mysql远程访问权限导致。 登录mysql&#xff0c;发现出现了 Access denied for user ‘root’ T’localhost (using password: YES) 此时先找到my.cnf文件,使用命令mysql --…

http跟https有什么区别?

HTTPS和HTTP的概念&#xff1a; HTTP&#xff1a;是互联网上应用最为广泛的一种网络协议&#xff0c;是一个客户端和服务器端请求和应答的标准&#xff08;TCP&#xff09;&#xff0c;用于从WWW服务器传输超文本到本地浏览器的传输协议&#xff0c;它可以使浏览器更加高效&am…

智慧灌溉解决方案(基于物联网的智能灌溉系统)

​ 详情&#xff1a;智慧水务数字孪生安全监测解决方案提供商-星创 (key-iot.com.cn) 随着农业IOT的快速发展,智慧灌溉正成为提高农业水资源利用效率,实现精准灌溉的重要技术手段。完整的智慧灌溉系统由实地各类传感设备以及后台管理软件平台组成,可以实现对整个灌区的监测和精…

python(17)--文件的输入/输出

前言 在Python中&#xff0c;文件文本操作是非常重要的&#xff0c;主要有以下几个原因&#xff1a; 数据持久性&#xff1a;当你需要长期存储数据&#xff0c;如用户的个人信息、交易记录或数据库元数据等&#xff0c;将数据保存在文件中是一种常见的方法。文件系统提供了持…

非线性最小二乘问题的数值方法 —— 从高斯-牛顿法到列文伯格-马夸尔特法 (I)

Title: 非线性最小二乘问题的数值方法 —— 从高斯-牛顿法到列文伯格-马夸尔特法 (I) 文章目录 前言I. 从高斯-牛顿法II. 到阻尼高斯-牛顿法III. 再到列文伯格-马夸尔特法1. 列文伯格-马夸尔特法的由来2. 列文伯格-马夸尔特法的说明说明一. 迭代方向说明二. 近似于带权重的梯度…

步进电机介绍

一、什么是步进电机&#xff1a; 步进电机是一种将电脉冲信号转换成相应角位移或线位移的电动机。每输入一个脉冲信号&#xff0c;转子就转动一个角度或前进一步&#xff0c;其输出的角位移或线位移与输入的脉冲数成正比&#xff0c;转速与脉冲频率成正比。因此&#xff0c;步…

通付盾受邀出席2024安全市场年度大会,荣获“数字安全产业杰出贡献奖”!

1月5日&#xff0c;由国内数字产业独立的第三方调研咨询机构数世咨询主办&#xff0c;以“数字安全&#xff0c;未来可期”为主题的2024安全市场年度大会在北京举办。来自国内网络安全厂商300多人以线上线下方式参加本次大会&#xff0c;通过4个多小时高能演讲&#xff0c;聚焦…

线性代数_同济第七版

contents 前言第1章 行列式1.1 二阶与三阶行列式1.1.1 二元线性方程组与二阶行列所式1.1.2 三阶行列式 1.2 全排列和对换1.2.1 排列及其逆序数1.2.2 对换 1.3 n 阶行列式的定义1.4 行列式的性质1.5 行列式按行&#xff08;列&#xff09;展开1.5.1 引理1.5.2 定理1.5.3 推论 * …

【Vue】项目使用px2rem

使用方法 1.安装包 npm i postcss-px2rem2.编写配置文件 编写核心代码&#xff0c;命名随意&#xff0c;我这里命名为px2rem并放在src/utils文件夹内 // 基准大小 const baseSize 100 // 设置 rem 函数 function setRem() {// 当前页面宽度相对于 1920 宽的缩放比例&#xf…

RK3568平台开发系列讲解(驱动篇)pinctrl 函数操作集结构体讲解

🚀返回专栏总目录 文章目录 一、pinctrl_ops二、pinmux_ops三、pinconf_ops沉淀、分享、成长,让自己和他人都能有所收获!😄 pinctrl_ops:提供有关属于引脚组的引脚的信息。pinmux_ops:选择连接到该引脚的功能。pinconf_ops:设置引脚属性(上拉,下拉,开漏,强度等)。…

安全防御之可信计算技术

可信计算技术是一种计算机安全体系结构&#xff0c;旨在提高计算机系统在面临各种攻击和威胁时的安全性和保密性。它通过包括硬件加密、受限访问以及计算机系统本身的完整性验证等技术手段&#xff0c;确保计算机系统在各种攻击和威胁下保持高度安全和保密性。 一、可信计算基…

WPS Office找回丢失的工作文件

WPS office恢复办公文件方法有两种. 1.通过备份中心可以查看近期编辑 office 历史版本进行恢复. 2.缓存备份目录可以查看编辑过的 office 文件的历史版本&#xff0c;新版本 WPS 可以在配置工具-备份清理找到&#xff0c;2019 年旧版本 WPS 可以在新建任意 office 文件-文件-选…

【Java 设计模式】设计原则之单一职责原则

文章目录 1. 定义2. 好处3. 应用4. 示例结语 在面向对象设计中&#xff0c;单一职责原则是一个重要的设计原则之一。它提倡一个类应该只有一个原因引起变化&#xff0c;即一个类应该只有一个职责。在本文中&#xff0c;我们将深入研究单一职责原则&#xff0c;了解它的定义、优…