[React源码解析] React的设计理念和源码架构 (一)

  1. 任务分割
  2. 异步执行
  3. 让出执法权

文章目录

      • 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的设计理念

  1. Fiber: 即对应真实dom, 又作为分隔单元。
  2. Scheduler: 用js实现一套时间片运行的机制, 使得requestIdleCallback()的浏览器的兼容性和触发不稳定的问题解决。
  3. Lane: 异步调度有了, 需要细粒度的管理各个任务的优先级, 让高优先级的先执行, 各个Fiber工作单元还能比较优先级, 优先级相同的一起执行。

上面的机制能实现batchedUpdates批量更新和Suspense

1.1 Fiber

Fiber: react15的更新是同步的,因为它不能将任务分割,所以需要一套数据结构让它既能对应真实的dom又能作为分隔的单元,这就是Fiber。

  1. 对应真实dom。
  2. 作为分割单元。
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的源码架构

  1. Scheduler(调度器): 排序优先级,让优先级高的任务先进行reconcile
  2. Reconciler(协调器): 找出哪些节点发生了改变,并打上不同的Flags(旧版本react叫Tag)
  3. 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构建相关

在这里插入图片描述

  1. react:核心Api如:React.createElement、React.Component都在这

  2. 和平台相关render相关的文件夹:
    react-art:如canvas svg的渲染
    react-dom:浏览器环境
    react-native-renderer:原生相关 react-noop-renderer:调试或者fiber用

  3. 试验性的包
    react-server: ssr相关
    react-fetch: 请求相关
    react-interactions: 和事件如点击事件相关
    react-reconciler: 构建节点
    shared:包含公共方法和变量

  4. 辅助包:
    react-is : 判断类型
    react-client: 流相关
    react-fetch: 数据请求相关

  5. react-refresh: 热加载相关

  6. scheduler:调度器相关

  7. React-reconciler:在render阶段用它来构建fiber节点

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

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

相关文章

项目进展(八)-编写代码,驱动ADS1285

一、代码 根据芯片的数据手册编写部分驱动&#xff0c;首先看部分引脚的波形&#xff1a; DRDY: CS&#xff1a; 首先在代码初始化时连续写入三个寄存器&#xff1a; void WriteReg(uint8_t startAddr, uint8_t *regData, uint8_t number) {uint8_t i0;// 循环写number1次…

基于STM32 ZigBee无线远程火灾报警监控系统物联网温度烟雾

实践制作DIY- GC00168---ZigBee无线远程监控系统 一、功能说明&#xff1a; 基于STM32单片机设计---ZigBee无线远程监控系统 二、功能说明&#xff1a; 1个主机&#xff1a;STM32F103C系列单片机LCD1602显示器蜂鸣器 ZigBee无线模块3个按键&#xff08;设置、加、减&#xff0…

剑指offer——JZ77 按之字形顺序打印二叉树 解题思路与具体代码【C++】

一、题目描述与要求 按之字形顺序打印二叉树_牛客题霸_牛客网 (nowcoder.com) 题目描述 给定一个二叉树&#xff0c;返回该二叉树的之字形层序遍历&#xff0c;&#xff08;第一层从左向右&#xff0c;下一层从右向左&#xff0c;一直这样交替&#xff09; 数据范围&#x…

【树】树的直径和重心

目录 一.树的直径 &#xff08;1&#xff09;定义 &#xff08;2&#xff09;思路 &#xff08;3&#xff09;例题 &#xff08;4&#xff09;std(第一小问) 二.树的重心 &#xff08;1&#xff09;介绍 &#xff08;2&#xff09;求重心 &#xff08;3&#xff09;例…

【逐步剖C】-第十一章-动态内存管理

一、为什么要有动态内存管理 从我们平常的学习经历来看&#xff0c;所开辟的数组一般都为固定长度大小的数组&#xff1b;但从很多现实需求来看需要我们开辟一个长度“可变”的数组&#xff0c;即这个数组的大小不能在建立数组时就指定&#xff0c;需要根据某个变量作为标准。…

我的创作纪念日-第1024天

文章目录 一、机缘二、收获三、日常四、憧憬 一、机缘 不知不觉&#xff0c;已经加入CSDN这个大家庭5年多了&#xff0c;回想起3年前发布第一篇博客的时候&#xff0c;那时我记得很清楚&#xff0c;我在做项目时遇到报错&#xff0c;解决问题之后&#xff0c;然后想起了好多人…

Ipython和Jupyter Notebook介绍

Ipython和Jupyter Notebook介绍 Python、IPython和Jupyter Notebook是三个不同但密切相关的工具。简而言之&#xff0c;Python是编程语言本身&#xff0c;IPython是对Python的增强版本&#xff0c;而Jupyter Notebook是一种在Web上进行交互式计算的环境&#xff0c;使用IPytho…

ChatGPT付费创作系统V2.3.4独立版 +WEB端+ H5端 + 小程序最新前端

人类小徐提供的GPT付费体验系统最新版系统是一款基于ThinkPHP框架开发的AI问答小程序&#xff0c;是基于国外很火的ChatGPT进行开发的Ai智能问答小程序。当前全民热议ChatGPT&#xff0c;流量超级大&#xff0c;引流不要太简单&#xff01;一键下单即可拥有自己的GPT&#xff0…

时序分解 | Matlab实现CEEMDAN完全自适应噪声集合经验模态分解时间序列信号分解

时序分解 | Matlab实现CEEMDAN完全自适应噪声集合经验模态分解时间序列信号分解 目录 时序分解 | Matlab实现CEEMDAN完全自适应噪声集合经验模态分解时间序列信号分解效果一览基本介绍程序设计参考资料 效果一览 基本介绍 Matlab实现CEEMDAN完全自适应噪声集合经验模态分解时间…

C++(STL容器适配器)

前言&#xff1a; 适配器也称配接器&#xff08;adapters&#xff09;在STL组件的灵活组合运用功能上&#xff0c;扮演着轴承、转换器的角色。 《Design Patterns》对adapter的定义如下&#xff1a;将一个class的接口转换为另一个class的接口&#xff0c;使原本因接口不兼容而…

Eureka

大家好我是苏麟今天带来Eureka的使用 . 提供者和消费者 在服务调用关系中&#xff0c;会有两个不同的角色&#xff1a; 服务提供者&#xff1a;一次业务中&#xff0c;被其它微服务调用的服务。&#xff08;提供接口给其它微服务&#xff09; 服务消费者&#xff1a;一次业务…

CCF CSP认证 历年题目自练 Day22

CCF CSP认证 历年题目自练 Day22 题目一 试题编号&#xff1a; 201912-1 试题名称&#xff1a; 报数 时间限制&#xff1a; 1.0s 内存限制&#xff1a; 512.0MB 题目分析&#xff08;个人理解&#xff09; 每一个人都要报多少个数字&#xff0c;我选择字典存储&#xff0…

私有云盘:lamp部署nextcloud+高可用集群

目录 一、实验准备&#xff1a; 二、配置mariadb主从复制 三台主机下载mariadb 1&#xff09;主的操作 2&#xff09;从的操作 3&#xff09;测试数据是否同步 三、配置nfs让web服务挂载 1、安装 2、配置nfs服务器 3、配置web服务的httpd 4、测试 四、web 服务器 配…

进程调度算法之时间片轮转调度(RR),优先级调度以及多级反馈队列调度

1.时间片轮转调度算法(RR) round Robin 1.算法思想 公平地、轮流地为各个进程服务&#xff0c;让每个进程在一定时间间隔内都可以得到响应。 2.算法规则 按照各进程到达就绪队列的顺序&#xff0c;轮流让各个进程执行一个时间片&#xff08;如100ms&#xff09;。 若进程未…

C/C++学习 -- HMAC算法

1. HMAC算法概述 HMAC&#xff0c;全称为HMAC-MD5、HMAC-SHA1、HMAC-SHA256等&#xff0c;是一种在数据传输中验证完整性和认证来源的方法。它结合了哈希函数和密钥&#xff0c;通过在数据上应用哈希函数&#xff0c;生成一个带密钥的散列值&#xff0c;用于验证数据的完整性。…

10.1 今日任务:select实现服务器并发

#include <myhead.h>#define ERR_MSG(msg) do{\fprintf(stderr, "__%d__:", __LINE__); \perror(msg);\ }while(0)#define PORT 8888 //端口号&#xff0c;范围1024~49151 #define IP "192.168.112.115" //本机IP&#xff0c;ifco…

【Vue3】定义全局变量和全局函数

// main.ts import { createApp } from vue import App from ./App.vue const app createApp(App)// 解决 ts 报错 type Filter {format<T>(str: T): string } declare module vue {export interface ComponentCustomProperties {$filters: Filter,$myArgs: string} }a…

计算机竞赛 题目:基于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 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 基于FP-Growth的新闻挖掘算法系统的设计与实现…

httpserver 下载服务器demo 以及libevent版本的 httpserver

实现效果如下&#xff1a; 图片可以直接显示 cpp h 这些可以直接显示 其他的 则是提示是否要下载 单线程 还有bug 代码如下 先放上来 #include "httpserver.h" #include "stdio.h" #include <stdlib.h> #include <arpa/inet.h> #include…

QScrollArea样式

简介 QScrollBar垂直滚动条分为sub-line、add-line、add-page、sub-page、up-arrow、down-arrow和handle几个部分。 QScrollBar水平滚动条分为sub-line、add-line、add-page、sub-page、left-arrow、right-arrow和handle几个部分。 部件如下图所示&#xff1a; 样式详…