[React源码解析] React的设计理念和源码架构 (一)
- 任务分割
- 异步执行
- 让出执法权
文章目录
- 1.React的设计理念
- 1.1 Fiber
- 1.2 Scheduler
- 1.3 Lane
- 1.4 代数效应
- 2.React的源码架构
- 2.1 大概图示
- 2.2 jsx
- 2.3 Fiber双缓存
- 2.4 scheduler
- 2.5 Lane模型
- 2.6 reconciler
- 2.7 renderer
- 2.8 concurrent
- 3.React源码调试
1.React的设计理念
- Fiber: 即对应真实dom, 又作为分隔单元。
- Scheduler: 用js实现一套时间片运行的机制, 使得requestIdleCallback()的浏览器的兼容性和触发不稳定的问题解决。
- Lane: 异步调度有了, 需要细粒度的管理各个任务的优先级, 让高优先级的先执行, 各个Fiber工作单元还能比较优先级, 优先级相同的一起执行。
上面的机制能实现batchedUpdates批量更新和Suspense
1.1 Fiber
Fiber: react15的更新是同步的,因为它不能将任务分割,所以需要一套数据结构让它既能对应真实的dom
又能作为分隔的单元
,这就是Fiber。
- 对应真实dom。
- 作为分割单元。
let firstFiber
let nextFiber = firstFiber
let shouldYield = false
//firstFiber->firstChild->sibling
function performUnitOfWork(nextFiber){//...return nextFiber.next
}function workLoop(deadline){while(nextFiber && !shouldYield){nextFiber = performUnitOfWork(nextFiber)shouldYield = deadline.timeReaming < 1}requestIdleCallback(workLoop)
}requestIdleCallback(workLoop)
1.2 Scheduler
Scheduler: 有了Fiber, 需要用浏览器的时间片异步执行这些Fiber的工作单元, 有一个Api是requestIdleCallback(), 可以在浏览器空闲的时候执行一些任务, 用这个api执行react的更新。requestIdleCallback存在着浏览器的兼容性和触发不稳定的问题, 需要用js实现一套时间片运行的机制, react称为Scheduler。
1.3 Lane
Lane: 异步调度有了, 需要细粒度的管理各个任务的优先级, 让高优先级的先执行, 各个Fiber工作单元还能比较优先级, 优先级相同的一起执行。
1.4 代数效应
除了cpu的瓶颈问题, 还存在一些副作用, 比如获取数据、文件操作等。不同设备性能和网络状况都不一样, react如何处理这些问题, 需要react可以有分离副作用的能力, 解耦, 这就是代数效应。
function getPrice(id) {return fetch(`xxx.com?id=${productId}`).then((res)=>{return res.price})
}async function getTotalPirce(id1, id2) {const p1 = await getPrice(id1);const p2 = await getPrice(id2);return p1 + p2;
}async function run(){await getTotalPrice('001', '002');
}
getPrice()是一个异步获取数据的方法, 可以用async+await的方式获取数据, 但是会导致调用getTotalPrice的run方法也会变成异步函数, 这就是async的传染性(副作用)。
function usePrice(id) {useEffect((id)=>{fetch(`xxx.com?id=${productId}`).then((res)=>{return res.price})}, [])
}function TotalPirce({id1, id2}) {const p1 = usePrice(id1);const p2 = usePrice(id2);return <TotalPirce props={...}>
}
getPrice换成usePrice, getTotalPirce换成TotalPirce组件, 这是hook的分离副作用能力。
generator: 也是有一定的传染性的, generator不能计算优先级, 排序优先级。
function getPrice(id) {return fetch(`xxx.com?id=${productId}`).then((res)=>{return res.price})
}function* getTotalPirce(id1, id2) {const p1 = yield getPrice(id1);const p2 = yield getPrice(id2);return p1 + p2;
}function* run(){yield getTotalPrice('001', '002');
}
解耦副作用在函数式编程的实践中非常常见, 如react-saga, 将副作用从saga中分离, 自己不处理副作用, 发请求处理。
function* fetchUser(action) {try {const user = yield call(Api.fetchUser, action.payload.userId);yield put({type: "USER_FETCH_SUCCEEDED", user: user});} catch (e) {yield put({type: "USER_FETCH_FAILED", message: e.message});}
}
2.React的源码架构
- Scheduler(调度器): 排序优先级,让优先级高的任务先进行reconcile
- Reconciler(协调器): 找出哪些节点发生了改变,并打上不同的Flags(旧版本react叫Tag)
- Renderer(渲染器): 将Reconciler中打好标签的节点渲染到视图上
2.1 大概图示
jsx(mount/update) -> scheduler(render) -> reconciler(render) -> rerender(commit)
2.2 jsx
jsx: React通过Babel解析, 将jsx转换成React.createElement, React.createElement方法返回virtual-dom对象React.createElement方法返回virtual-dom对象, 所有jsx本质上就是React.createElement的语法糖。
createElement -> ReactElement
export function createElement(type, config, children) {let propName;// Reserved names are extractedconst props = {};let key = null;let ref = null;let self = null;let source = null;if (config != null) {if (hasValidRef(config)) {ref = config.ref;if (__DEV__) {warnIfStringRefCannotBeAutoConverted(config);}}if (hasValidKey(config)) {key = '' + config.key;}self = config.__self === undefined ? null : config.__self;source = config.__source === undefined ? null : config.__source;// Remaining properties are added to a new props objectfor (propName in config) {if (hasOwnProperty.call(config, propName) &&!RESERVED_PROPS.hasOwnProperty(propName)) {props[propName] = config[propName];}}}// Children can be more than one argument, and those are transferred onto// the newly allocated props object.const childrenLength = arguments.length - 2;if (childrenLength === 1) {props.children = children;} else if (childrenLength > 1) {const childArray = Array(childrenLength);for (let i = 0; i < childrenLength; i++) {childArray[i] = arguments[i + 2];}if (__DEV__) {if (Object.freeze) {Object.freeze(childArray);}}props.children = childArray;}// Resolve default propsif (type && type.defaultProps) {const defaultProps = type.defaultProps;for (propName in defaultProps) {if (props[propName] === undefined) {props[propName] = defaultProps[propName];}}}if (__DEV__) {if (key || ref) {const displayName =typeof type === 'function'? type.displayName || type.name || 'Unknown': type;if (key) {defineKeyPropWarningGetter(props, displayName);}if (ref) {defineRefPropWarningGetter(props, displayName);}}}return ReactElement(type,key,ref,self,source,ReactCurrentOwner.current,props,);
}const ReactElement = function(type, key, ref, self, source, owner, props) {const element = {// This tag allows us to uniquely identify this as a React Element$$typeof: REACT_ELEMENT_TYPE,// Built-in properties that belong on the elementtype: type,key: key,ref: ref,props: props,// Record the component responsible for creating this element._owner: owner,};if (__DEV__) {// The validation flag is currently mutative. We put it on// an external backing store so that we can freeze the whole object.// This can be replaced with a WeakMap once they are implemented in// commonly used development environments.element._store = {};// To make comparing ReactElements easier for testing purposes, we make// the validation flag non-enumerable (where possible, which should// include every environment we run tests in), so the test framework// ignores it.Object.defineProperty(element._store, 'validated', {configurable: false,enumerable: false,writable: true,value: false,});// self and source are DEV only properties.Object.defineProperty(element, '_self', {configurable: false,enumerable: false,writable: false,value: self,});// Two elements created in two different places should be considered// equal for testing purposes and therefore we hide it from enumeration.Object.defineProperty(element, '_source', {configurable: false,enumerable: false,writable: false,value: source,});if (Object.freeze) {Object.freeze(element.props);Object.freeze(element);}}return element;
};
2.3 Fiber双缓存
Fiber对象上面保存了包括这个节点的属性, dom, 类型, 通过child, sibling, reture 形成fiber树, 保存了更新状态时用于计算state的updateQueue, updateQueue为一个链表, 上面存在未计算的update, update也是一个数据结构, 上面存有更新的数据、优先级等, 还有副作用的信息。
双缓存是指存在两颗Fiber树, current Fiber树描述了当前呈现的dom树, workInProgress Fiber是正在更新的Fiber树, 这两树都是存在于内存, 在workInProgress Fiber构建完成之后会将它作为current Fiber应用到dom上。
function App() {const [count, setCount] = useState(0);return (<><h1 onClick={() => {setCount(() => count + 1);}}><p title={count}>{count}</p> </h1></>)
}ReactDOM.render(<App />, document.getElementById("root"));
2.4 scheduler
Scheduler的作用是调度任务。
在Scheduler中的每个任务的优先级使用过期时间表示的, 如果一个任务的过期时间离现在很近, 说明要过期了, 优先级很高。
没有过期的放在timerQueue中, 过期的放taskQueue, timerQueue和taskQueue都是小顶堆, 所以peek出的都是离现在时间最近也就是优先级最高的那个任务。
2.5 Lane模型
优先级表示方法Lane: Lane使用二进制的方式表示优先级, 1表示位置, 同一个二进制数可以有多个相同优先级的位, 这就可以表示‘批’的概念。低优先级的任务如果被高优先级的任务一直打断, 等到达它的时候, 优先级自动变为最高。
bit越多, 优先级越低。
//ReactFiberLane.js
export const NoLanes: Lanes = /* */ 0b0000000000000000000000000000000;
export const NoLane: Lane = /* */ 0b0000000000000000000000000000000;export const SyncLane: Lane = /* */ 0b0000000000000000000000000000001;
export const SyncBatchedLane: Lane = /* */ 0b0000000000000000000000000000010;export const InputDiscreteHydrationLane: Lane = /* */ 0b0000000000000000000000000000100;
const InputDiscreteLanes: Lanes = /* */ 0b0000000000000000000000000011000;const InputContinuousHydrationLane: Lane = /* */ 0b0000000000000000000000000100000;
const InputContinuousLanes: Lanes = /* */ 0b0000000000000000000000011000000;export const DefaultHydrationLane: Lane = /* */ 0b0000000000000000000000100000000;
export const DefaultLanes: Lanes = /* */ 0b0000000000000000000111000000000;const TransitionHydrationLane: Lane = /* */ 0b0000000000000000001000000000000;
const TransitionLanes: Lanes = /* */ 0b0000000001111111110000000000000;const RetryLanes: Lanes = /* */ 0b0000011110000000000000000000000;export const SomeRetryLane: Lanes = /* */ 0b0000010000000000000000000000000;export const SelectiveHydrationLane: Lane = /* */ 0b0000100000000000000000000000000;const NonIdleLanes = /* */ 0b0000111111111111111111111111111;export const IdleHydrationLane: Lane = /* */ 0b0001000000000000000000000000000;
const IdleLanes: Lanes = /* */ 0b0110000000000000000000000000000;export const OffscreenLane: Lane = /* */ 0b1000000000000000000000000000000;
2.6 reconciler
Reconciler发生在render阶段, render分为节点执行beginWork和completeWork, 或是计算state, 对比节点的差异, 为节点赋值相应的effectFlags。
Reconciler会创建或者更新Fiber节点。在mount的时候会根据jsx生成Fiber对象,在update的时候会根据最新的state形成的jsx对象和current Fiber树对比构建workInProgress Fiber树, 对比就是diff算法。
diff算法发生在render阶段的reconcileChildFibers函数中, diff算法分为单节点的diff和多节点的diff, 单节点会根据节点的key和type, props判断节点是复用还是直接新创建节点, 多节点diff会涉及节点的增删和节点位置的变化。
reconcile时会在这些Fiber上打上Flags标签, 在commit阶段把这些标签应用到真实dom上, 这些标签代表了节点的增删改。
//ReactFiberFlags.js
export const Placement = /* */ 0b0000000000010;
export const Update = /* */ 0b0000000000100;
export const PlacementAndUpdate = /* */ 0b0000000000110;
export const Deletion = /* */ 0b0000000001000;
render阶段遍历Fiber树类似dfs的过程, ‘捕获’阶段发生在beginWork函数中, 该函数做的主要工作是创建Fiber节点, 计算state和diff算法, ‘冒泡’阶段发生在completeWork中, 该函数主要是做一些收尾工作, 例如处理节点的props、和形成一条effectList的链表, 该链表是被标记了更新的节点形成的链表。
function App() {return (<><h1><p>count</p> xiaochen</h1></>)
}
function App() {const [count, setCount] = useState(0);return (<><h1onClick={() => {setCount(() => count + 1);}}><p title={count}>{count}</p> xiaochen</h1></>)
}
如果p和h1节点更新了则effectList如下, rootFiber->h1->p, fiberRoot是整个项目的根节点, rootFiber为应用的根节点, 可以有多个。
2.7 renderer
Renderer发生在commit阶段, commit阶段遍历effectList执行对应的dom操作或部分生命周期。并执行真实dom节点的操作和一些生命周期, 不同的平台对应的Renderer不同, 浏览器对应的是react-dom。
commit阶段发生在commitRoot函数中, 遍历effectList, 三个函数来处理effectList上的节点, commitBeforeMutationEffects, commitMutationEffects, commitLayoutEffects。
2.8 concurrent
一类功能的合集(如fiber、schduler、lane、suspense), 目的是为了提高应用的响应速度, 使应用cpu密集型的更新不在那么卡顿, 核心是实现了一套异步可中断、带优先级的更新。
3.React源码调试
- fixtures:为代码贡献者提供的测试React
- packages:主要部分,包含Scheduler,reconciler等
- scripts:react构建相关
-
react:核心Api如:React.createElement、React.Component都在这
-
和平台相关render相关的文件夹:
react-art:如canvas svg的渲染
react-dom:浏览器环境
react-native-renderer:原生相关 react-noop-renderer:调试或者fiber用 -
试验性的包
react-server: ssr相关
react-fetch: 请求相关
react-interactions: 和事件如点击事件相关
react-reconciler: 构建节点
shared:包含公共方法和变量 -
辅助包:
react-is : 判断类型
react-client: 流相关
react-fetch: 数据请求相关 -
react-refresh: 热加载相关
-
scheduler:调度器相关
-
React-reconciler:在render阶段用它来构建fiber节点
相关文章:

[React源码解析] React的设计理念和源码架构 (一)
任务分割异步执行让出执法权 文章目录 1.React的设计理念1.1 Fiber1.2 Scheduler1.3 Lane1.4 代数效应 2.React的源码架构2.1 大概图示2.2 jsx2.3 Fiber双缓存2.4 scheduler2.5 Lane模型2.6 reconciler2.7 renderer2.8 concurrent 3.React源码调试 1.React的设计理念 Fiber: 即…...

[论文工具] LaTeX论文撰写常见用法及实战技巧归纳(持续更新)
祝大家中秋国庆双节快乐! 回过头来,我们在编程过程中,经常会遇到各种各样的问题。然而,很多问题都无法解决,网上夹杂着各种冗余的回答,也缺乏系统的实战技巧归纳。为更好地从事科学研究和编程学习ÿ…...

多媒体应用设计师
1.多媒体技术基础 1.1.媒体与技术 1.1.媒体 维基百科:传播信息载体 国际电信联盟(ITU-T):感知、表示、存储和传输的手段和方法。 两层含义:存储信息的实体,媒质。传递信息载体,媒介。 1.2.国…...

socket.error: [Errno 10049]错误
今天在pycharm运行rl_server_no_training.py欲启动服务器时,却出现如下错误 Traceback (most recent call last):File "xxx/rl_server_no_training.py", line 333, in <module>main()File "xxx/rl_server_no_training.py", line 326, in…...

二叉树的经典OJ题
对称二叉树 1.题目2.图形分析3.代码实现 1.题目 2.图形分析 3.代码实现 class Solution {public boolean isSymmetric(TreeNode root) {if(root null){return true;}return isSymmetricchild(root.left,root.right);}private boolean isSymmetricchild(TreeNode leftTree,Tre…...
统一建模语言UML(1~8章在线测试参考答案)
目录 UML概述 UML概念模型 参与者和用例 用例图之间的关系 用例模型 类图中的类 类图建模 顺序图的构成 UML概述 一 单项选择题(3分) 1、关于UML描述不正确的是()。(1分) UML是由信息系统和面向对象领域三位专家Grady Booch、James Rumbaugh和Ivar Jac…...

计算机竞赛 题目:基于FP-Growth的新闻挖掘算法系统的设计与实现
文章目录 0 前言1 项目背景2 算法架构3 FP-Growth算法原理3.1 FP树3.2 算法过程3.3 算法实现3.3.1 构建FP树 3.4 从FP树中挖掘频繁项集 4 系统设计展示5 最后 0 前言 🔥 优质竞赛项目系列,今天要分享的是 基于FP-Growth的新闻挖掘算法系统的设计与实现…...
String 类型的变量和常量做 “+” 运算时发生了什么?
先看看字符串不加 final 关键字拼接的情况(jdk1.8): String str1 "str" String str2 "ing" String str3 "str" "ing" String str4 str1 str2 String str5 "string" System.out.println(str3 …...
【Java互联网技术】MinIO分布式文件存储服务
应用场景 互联网海量非结构化数据的存储 基本概念 Object:存储的基本对象,如文件、字节流等 Bucket:存储Object的逻辑空间,相当于顶层文件夹 Drive:存储数据的磁盘,在MinIO启动时,以参数的…...

在visual studio里配置Qt插件并运行Qt工程
Qt插件,也叫qt-vsaddin,它以*.vsix后缀名结尾。visual studio简称为VS,从visual studio 2010版本开始,VS支持Qt框架的开发,Qt以插件方式集成到VS里。这里简述在visual studio 2019里配置Qt 5.14.2插件,并配…...

【C语言】利用数组处理批量数据(字符数组)
前言:前面已经介绍了,字符数据是以字符的ASCII代码存储在存储单元中的,一般占一个字节。由于ASCII代码也属于整数形式,因此在C99标准中,把字符类型归纳为整型类型中的一种。 💖 博主CSDN主页:卫卫卫的个人主页 &#x…...

算法通过村第十二关-字符串|白银笔记|经典面试题
文章目录 前言1. 反转问题1.1 反转字符串1.2 k个一组反转1.3 仅仅反转字母1.3.1 采用栈实现操作1.3.2 采用双指针实现操作 1.4 反转字符串里面的单词1.4.1 使用语言提供的方法来解决(内置API)1.4.2 如何优雅自己实现上述功能 2. 验证回文串3. 字符串中的第一个唯一字符4. 判断是…...

《视觉 SLAM 十四讲》V2 第 5 讲 相机与图像
文章目录 相机 内参 && 外参5.1.2 畸变模型单目相机的成像过程5.1.3 双目相机模型5.1.4 RGB-D 相机模型 实践5.3.1 OpenCV 基础操作 【Code】OpenCV版本查看 5.3.2 图像去畸变 【Code】5.4.1 双目视觉 视差图 点云 【Code】5.4.2 RGB-D 点云 拼合成 地图【Code】 习题题…...
使用libmodbus库开发modbusTcp从站(支持多个主站连接)
使用libmodbus库开发modbusTcp从站(支持多个主站连接) Chapter1 使用libmodbus库开发modbusTcp从站(支持多个主站连接)rdsmodbusslave.hrdsmodbusslave.cppmain.cpp Chapter1 使用libmodbus库开发modbusTcp从站(支持多个主站连接) 参考链接:…...

GPT系列论文解读:GPT-2
GPT系列 GPT(Generative Pre-trained Transformer)是一系列基于Transformer架构的预训练语言模型,由OpenAI开发。以下是GPT系列的主要模型: GPT:GPT-1是于2018年发布的第一个版本,它使用了12个Transformer…...

(四)激光线扫描-光平面标定
在上一章节,已经实现了对激光线条的中心线提取,并且在最开始已经实现了对相机的标定,那么相机标定的作用是什么呢? 就是将图像二维点和空间三维点之间进行互相转换。 1. 什么是光平面 激光发射器投射出一条线,形成的一个扇形区域平面就是光平面,也叫光刀面,与物体相交…...

妙不可言的Python之旅----(二)
Python基础语法 什么是字面量 字面量:在代码中,被写下来的的固定的值,称之为字面量 常用的值类型 类型 描述 说明 数字(Number) 支持 • 整数(int) • 浮点数(floatÿ…...

cartographer(1)-运行
1.下载数据集 #1.下载数据集: mkdir /home/tang/bagfiles#2.开始二维建图 cd /home/tang/carto_ws/cartographer_detailed_comments_ws/install_isolated/source install_isolated/setup.bash rospack profile #新装的包索引地址存在ros的环境里 roslaunch ca…...

C++:模板进阶与继承
模板进阶与继承 模板进阶1.非类型的模板参数2.模板的特化2.1特化的概念2.2函数模板特化2.3类模板特化2.4全特化和偏特化2.4.1全特化2.4.2偏特化 3.模板的分离编译3.1同文件分离3.2不同文件下分离 继承1.继承的概念和定义1.1继承的概念1.2继承的定义1.2.1定义格式1.2.2继承关系和…...

vue-img-cutter 实现图片裁剪[vue 组件库]
借助 vue-img-cutter 可以在网页端实现图片裁剪功能,最终功能效果如下: 组件 npm 安装 npm install vue-img-cutter2 --save-dev # for vue2 npm install vue-img-cutter3 --save-dev # for vue3vue-img-cutter使用 template模板标签模块,…...

CTF show Web 红包题第六弹
提示 1.不是SQL注入 2.需要找关键源码 思路 进入页面发现是一个登录框,很难让人不联想到SQL注入,但提示都说了不是SQL注入,所以就不往这方面想了 先查看一下网页源码,发现一段JavaScript代码,有一个关键类ctfs…...
java_网络服务相关_gateway_nacos_feign区别联系
1. spring-cloud-starter-gateway 作用:作为微服务架构的网关,统一入口,处理所有外部请求。 核心能力: 路由转发(基于路径、服务名等)过滤器(鉴权、限流、日志、Header 处理)支持负…...

K8S认证|CKS题库+答案| 11. AppArmor
目录 11. AppArmor 免费获取并激活 CKA_v1.31_模拟系统 题目 开始操作: 1)、切换集群 2)、切换节点 3)、切换到 apparmor 的目录 4)、执行 apparmor 策略模块 5)、修改 pod 文件 6)、…...
【位运算】消失的两个数字(hard)
消失的两个数字(hard) 题⽬描述:解法(位运算):Java 算法代码:更简便代码 题⽬链接:⾯试题 17.19. 消失的两个数字 题⽬描述: 给定⼀个数组,包含从 1 到 N 所有…...

YSYX学习记录(八)
C语言,练习0: 先创建一个文件夹,我用的是物理机: 安装build-essential 练习1: 我注释掉了 #include <stdio.h> 出现下面错误 在你的文本编辑器中打开ex1文件,随机修改或删除一部分,之后…...

剑指offer20_链表中环的入口节点
链表中环的入口节点 给定一个链表,若其中包含环,则输出环的入口节点。 若其中不包含环,则输出null。 数据范围 节点 val 值取值范围 [ 1 , 1000 ] [1,1000] [1,1000]。 节点 val 值各不相同。 链表长度 [ 0 , 500 ] [0,500] [0,500]。 …...

高危文件识别的常用算法:原理、应用与企业场景
高危文件识别的常用算法:原理、应用与企业场景 高危文件识别旨在检测可能导致安全威胁的文件,如包含恶意代码、敏感数据或欺诈内容的文档,在企业协同办公环境中(如Teams、Google Workspace)尤为重要。结合大模型技术&…...

接口自动化测试:HttpRunner基础
相关文档 HttpRunner V3.x中文文档 HttpRunner 用户指南 使用HttpRunner 3.x实现接口自动化测试 HttpRunner介绍 HttpRunner 是一个开源的 API 测试工具,支持 HTTP(S)/HTTP2/WebSocket/RPC 等网络协议,涵盖接口测试、性能测试、数字体验监测等测试类型…...

c++第七天 继承与派生2
这一篇文章主要内容是 派生类构造函数与析构函数 在派生类中重写基类成员 以及多继承 第一部分:派生类构造函数与析构函数 当创建一个派生类对象时,基类成员是如何初始化的? 1.当派生类对象创建的时候,基类成员的初始化顺序 …...
Java求职者面试指南:Spring、Spring Boot、Spring MVC与MyBatis技术解析
Java求职者面试指南:Spring、Spring Boot、Spring MVC与MyBatis技术解析 一、第一轮基础概念问题 1. Spring框架的核心容器是什么?它的作用是什么? Spring框架的核心容器是IoC(控制反转)容器。它的主要作用是管理对…...