优质博文:IT-BLOG-CN
一、简介
为什么我要用react?JQuery也挺好的呀?
1、因为浏览器和JavaScript一直在更新,新版前端框架可以更好对接新的API,更好的利用浏览器的能力, 提供更新潮强大的功能。
2、react有其庞大的生态系统,如插件、工具、库等,对开发者更省力
二、React主要发展历程
2011 Facebook内部使用
2013 Facebook开源
2015 Facebook发布React Native(干掉了iOS和Android开发岗位)
2017 React发布Fiber架构
2019 React16.8引入Hooks
2022 React 18 发布Concurrent Mode和Server Component
2024 React 19RC发布
三、React特性
1、组件化: 代码复用度高
2、虚拟DOM: 减少不必要的DOM操作和渲染
3、JSX: 易于理解和使用
4、跨平台: ReactNative
5、Concurrent Mode: 可中断的异步更新
四、React原理
React15架构:
1、Reconciler(协调器)—— 负责找出变化的组件
2、Renderer(渲染器)—— 负责将变化的组件渲染到页面上
此架构弊端: Reconciler是递归渲染,过程不可中断,当更新任务过大用时过久就会造成页面卡顿
React16架构:
1、Scheduler(调度器)—— 调度任务的优先级,高优任务优先进入
2、ReconcilerReconciler(协调器)—— 负责找出变化的组件
3、Renderer(渲染器)—— 负责将变化的组件渲染到页面上
此架构基于Fiber,时间切片,优先级调度,实现了渲染过程的可中断更新,在浏览器资源上限内最高效率流畅更新页面。
实现步骤:
1.虚拟DOM一个用来描述 UI 结构的 JavaScript 对象,用于抽象表示浏览器中真实的DOM节点,体积轻量,不绑定实际DOM具有的属性和方法。
// 虚拟 DOM 对象
const virtualDOM = {
type: 'h1',
props: {
className: 'greeting',
children: 'Hello, world!'
}
};
虚拟DOM的作用
每一个浏览器引擎都包含渲染引擎和JS引擎,二者各司其职。JS引擎执行JS快速不费力,可对用户不可见。
渲染引擎渲染页面比较耗费浏览器资源,直接和用户交互。
在接收到DOM更新任务时,虚拟DOM可作为真实DOM替身,缓冲计算好,最后统一更新到真实DOM树上,减少不必要的对DOM的操作和渲染。
当虚拟DOM有唯一标识时,还可以实现对应真实DOM节点的复用。
React Fiber 架构
Fiber Node: 一个典型的 Fiber 节点包含以下几个关键属性:
1、type: 组件的类型(例如函数组件、类组件、DOM 元素等)。
key: 用于标识节点的唯一键。
stateNode: 与 Fiber 节点对应的实际 DOM 节点或组件实例。
child: 指向第一个子节点的指针。sibling:指向下一个兄弟节点的指针。
return: 指向父节点的指针。
pendingProps: 新的属性,在 Fiber 树更新时使用。
memoizedProps: 上一次渲染时的属性。
memoizedState: 上一次渲染时的状态。
alternate: 指向当前 Fiber 节点的另一个版本(用于双缓存机制)。
Fiber Tree: 通过每个Fiber的child,sibling,return节点链接形成的链表结构。这种结构使得 React 可以在不影响其他节点的情况下,灵活地插入、删除或更新Fiber节点。React 遍历 Fiber 树时,通常使用深度优先搜索(DFS)。
双缓存机制: 在React中最多会同时存在两棵Fiber树。当前屏幕上显示内容对应的Fiber树称为current Fiber树,正在内存中构建的Fiber树称为workInProgress Fiber树。
current Fiber树中的Fiber节点被称为current fiber,workInProgress Fiber树中的Fiber节点被称为workInProgress fiber,他们通过alternate属性连接。
React应用的根节点通过使current指针在不同Fiber树的rootFiber间切换来完成current Fiber树指向的切换。
即当workInProgress Fiber树构建完成交给Renderer渲染在页面上后,应用根节点的current指针指向workInProgress Fiber树,此时workInProgress Fiber树就变为current Fiber树。
这种在内存中构建并直接替换的技术叫做双缓存。
时间切片
前面提到的每一个浏览器引擎都包含渲染引擎和JS引擎,这两个引擎的工作是互斥的,同一时刻只能工作一个。
浏览器的刷新频率受限于显示器的刷新频率。以60Hz为例,浏览器每1000ms刷新16次,说明浏览器的一个刷新周期是16.6ms。
在浏览器每个刷新周期内,会按次序执行以下工作,JS脚本执行 ----- 样式布局 ----- 样式绘制
其中的JS脚本执行时间如果过长,就会导致刷新周期内无法执行样式绘制,形成掉帧,造成用户视觉上的页面卡顿。
为了解决这种问题,于是诞生了时间切片。
React,在浏览器每一帧刷新周期内,预留一些时间给 JS 线程,利用这部分时间更新组件(可以看到,在源码中,预留的初始时间是 5ms)。
当预留的时间不足以执行完全部的JS时,React将线程控制权交还给浏览器使其有时间渲染 UI,React则等待下一帧时间到来继续被中断的工作。
function performUnitOfWork(fiber) {
// 处理当前 Fiber 节点
beginWork(fiber);
// 如果有子节点,处理子节点
if (fiber.child) {
return fiber.child;
}
// 如果没有子节点,处理兄弟节点或返回父节点
let nextFiber = fiber;
while (nextFiber) {
completeWork(nextFiber);
if (nextFiber.sibling) {
return nextFiber.sibling;
}
nextFiber = nextFiber.return;
}
}
function workLoop(deadline) {
let shouldYield = false;
while (nextUnitOfWork && !shouldYield) {
nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
shouldYield = deadline.timeRemaining() < 1;
}
if (!nextUnitOfWork) {
commitRoot();
} else {
requestIdleCallback(workLoop);
}
}
// 开始渲染
requestIdleCallback(workLoop);
优先级调度
优先级级别: React 定义了多个优先级级别,用于区分不同类型的任务。主要的优先级级别包括:
1、Immediate: 立即执行的任务,通常用于用户输入等需要立刻响应的操作。如文本框输入、按钮点击等。
2、User-blocking: 用户阻塞任务,稍低于立即执行的任务,但仍然需要快速响应,影响用户界面交互流畅性。如拖拽、滑动等。
3、Normal: 正常优先级任务,常规更新,如组件的状态更新、网络请求完成后的数据处理等。Low:低优先级任务,如后台数据同步,定时器。
4、Idle: 空闲优先级任务,只有在浏览器空闲时才会执行。
优先级实现: lane模型借鉴了同样的概念,使用31位的二进制表示31条赛道,位数越小的赛道优先级越高,某些相邻的赛道拥有相同优先级。
调度流程:
1、当需要执行一个任务时,React 会创建一个任务对象,并将其添加到优先级队列中。任务对象包含任务的优先级、执行回调和其他相关信息。
2、调度器会根据任务的优先级对队列中的任务进行排序。高优先级任务会排在队列的前面,低优先级任务则排在后面。
3、调度器会从队列中取出最高优先级的任务并执行它。如果当前有更高优先级的任务到来,调度器会中断当前任务,优先执行高优先级任务。
4、调度器会根据浏览器的空闲时间和任务的优先级来调度任务的执行,确保高优先级任务能够及时响应。(React 使用 requestIdleCallback 和 requestAnimationFrame 等浏览器 API 来实现这一点。