React Fiber
~~React 16.8
之前的虚拟DOM
协调算法,在进行虚拟DOM
向真实DOM
更新时,会一直占据浏览器资源,导致用户触发的事件无法得到响应,给用户一种卡顿的感觉。React-Fiber
可以暂停页面的渲染,让浏览器先执行更高级的任务(比如响应用户触发的事件),等浏览器空闲后再恢复渲染,可以提高浏览器的用户响应速度,并兼顾任务执行效率;延时对DOM
的操作,避免一次操作大量DOM
~~节点。
对于Fiber
的理解,就是React
在React16.8
版本引进了Fiber
的概念。相对于以前的版本,Fiber
实质上就是在React
以前的diff
算法中多增加了一个调和阶段。其中requestAnimationFrame
是安排优先级高的函数在下一个动画帧之前被调用;requestIdleCallback
是安排优先级低的函数在帧结束时的空闲时间被调用。通过使用requestAnimationFrame``和``requestIdleCallback``,React-Fiber
可以暂停页面的渲染,让浏览器先执行更高级的任务(比如响应用户触发的事件),等浏览器空闲后再恢复页面渲染,可以提高浏览器的用户响应速度,并兼顾任务执行效率;同时也延时对DOM
的操作,避免一次操作大量DOM
节点。
Fiber的Scheduler、Reconciler、Renderer
三个Fiber API的作用
Scheduler
(调度器):排序优先级,让优先级高的任务进行reconcile
(调和)。比如:交互事件的优先级最高,要优先把与用户进行交互的事件排到最前面。Reconciler
(调和器):找出哪些节点发生了变化,并打标签Renderer
(渲染器):将Reconciler
中打好标签的节点渲染到页面上(属于commit
阶段)
Fiber API的使用
React
渲染分为首次渲染和更新渲染,这两个渲染的过程都包括render
和commit
两个阶段。
- 对于首次渲染,
React
主要的工作就是使用babel
对jsx
进行词法分析和语法分析,然后调用React.createElement
方法将jsx
对象转化为Fiber
树,并根据Fiber
树的层级关系,构建生成DOM
树并渲染至屏幕中 - 对于更新渲染时,
Fiber
树已经存在于内存中了,所以React
更关心的是计算出Fiber
树中各个节点的差异,并更新到屏幕中。利用Fiber
的双缓冲技术,双缓冲共有两颗Fiber树
,一棵为current树
,展示到页面上的;另一棵是WorkInProgress树
,存在于内存中,用来计算变化,然后直接替换current树
。先在内存中计算改变,计算完成后一次性更新,这样用户就不会感知到明显的计算变化
两个阶段
render
阶段:利用Fiber
双缓冲技术在内存中构造一棵Fiber
树,在其上进行调和计算,找到需要更新的节点并记录,这个过程会被重复中断恢复执行(时间片、主线程让给浏览器执行更高级的任务)commit
阶段:根据render
阶段的计算结果,执行更新操作,这个过程是同步执行的。
React
内部维护了一条执行副作用的单向链表effectList
,这些副作用对应的DOM
操作在commit
阶段执行,除此之外,一些生命周期函数、useEffect
等也在commit
阶段执行。
commit
阶段的工作主要分为三个部分:
before mutation
阶段(执行DOM
操作前):变量赋值、状态重置、调度useEffect
mutation
阶段(执行DOM
操作):创建DOM
、遍历effectList
layout
阶段(执行DOM
操作后):可能会触发更新,开启新的render-commit
阶段
Fiber的双缓冲技术
Fiber
的双缓冲指的是将需要变化的部分,先在内存中计算改变,计算完成后一次性更新,这样用户就不会感知到明显的计算变化。双缓冲共有两颗Fiber树
,一棵为current树
,展示到页面上的;另一棵是WorkInProgress树
,存在于内存中,用来计算变化,然后直接替换current树
。双缓冲技术最主要的思想就是先在内存中计算改变,计算完成后一次性更新,这样当用户使用时就不会感觉有卡顿的情况。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0jkBXKj4-1663847199205)(https://secure2.wostatic.cn/static/cZxr8ZyiYw3QVWMwjbo8Kj/v2-f81520d347cc22b36ad5868acb07e6ec_720w.jpg)]
Fiber可中断执行的原理
(Fiber是一个协程,可以随时中断执行)
Fiber
的可中断、可继续是基于浏览器的 requestIdleCallback api
实现的。目前大多浏览设备每秒刷新60
次,也就是16.6ms
一次,requestIdleCallback
函数每一帧会返回这一秒渲染完成后剩余的时间,React
就会检查现在还剩多少时间,如果没有时间就将控制权交还浏览器;然后继续进行下一帧的渲染。
requestIdleCallback
接收两个入参,一个是回调函数,一个是到期时间。这个回调函数的入参也有个入参叫做deadline
对象,这个对象的timeRemaining
方法会返回当前帧还剩余的时间有多少毫秒。到期时间则是告诉浏览器当这个时间到期时,不管当前帧有没有空闲时间,都必须执行当前帧注册的回调函数。
workLoop
就是requestIdleCallback
的回调函数。React
为了在重新渲染界面的时不阻塞浏览器主线程,将渲染任务分为了多个小模块,workLoop
就是执行这些小模块任务的函数。workLoop
方法首先判断当前帧还有没有时间,如果有空余时间,就继续执行wookloop
,如果当前帧没有时间,就让出执行权。浏览器就会在下一帧的空闲时间片内执行工作循环workLoop
。这样就实现了浏览器在每一帧的剩余时间片段内都会执行wookLoop
函数。这个工作循环会一直执行,即便里面没有任务可执行。这就是Fiber
可中断执行的原理。
总结
在 v16
之前的 React
里,是直接递归遍历 vdom
,通过 dom api
增删改 dom
的方式来渲染的。但当 vdom
过大,频繁调用 dom api
会比较耗时,而且递归又不能打断,所以有性能问题。
后来就引入了 fiber
架构,先把 vdom 树
转成 fiber
链表,然后再渲染 fiber
。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-apdjpa5r-1663847199206)(https://secure2.wostatic.cn/static/pRm9xbAAWiTT3aL4YPwXGn/1661399393348.png)]
vdom
转 fiber
的过程叫做 调和阶段(reconcile
),是可打断的,React
加入了任务调度 (schedule
) 的机制在空闲时调度 reconcile
,reconcile
的过程中会做 diff
,打上增删改的标记(effectTag
),并把对应的 dom
创建好。然后就可以一次性把 fiber
渲染到 dom
,也就是 commit
。
这个 schdule
、reconcile
、commit
的流程就是 fiber
架构。
Fiber
可中断执行的原理主要由浏览器的requestIdleCallback
方法实现。requestIdleCallback
接收两个参数,一个回调函数workLoop
,一个到期时间。React
为了在重新渲染界面的时不阻塞浏览器主线程,将渲染任务分为了多个小模块,workLoop
就是执行这些小模块任务的函数。workLoop
方法首先判断当前帧还有没有时间,如果有空余时间,就继续执行wookloop
,如果当前帧没有时间,就让出执行权。浏览器就会在下一帧的空闲时间片内执行workLoop
。这样就实现了浏览器在每一帧的剩余时间片段内都会执行wookLoop
函数。这就是Fiber
可中断执行的原理。
function lowPriorityWork(deadline) {
while (deadline.timeRemaining() > 0 && workList.length > 0)
performUnitOfWork();
if (workList.length > 0) requestIdleCallback(lowPriorityWork);
}
Fiber 链表
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QgOWecNP-1663847199206)(https://secure2.wostatic.cn/static/pwdqLW4qJYf3yVnhyjHmfP/image.png)]
React
实现了一种树型链表遍历算法。这种算法可以停止遍历并阻止栈增长.不再用递归实现遍历树的。
React
为了在重新渲染界面的时不阻塞浏览器主线程,将渲染任务分为了多个小模块,如果使用树的结构,React
只能不断递归,无法将渲染任务转换为一个个增量模块。所以React
使用Fiber
将树转为树型链表的结构。
如上图所示,每一个element
都会对应一个fiber
,多个fiber
是通过return
(指向父)、child
(指向子)、sibling
(指向兄弟)三个属性建立起联系的。
进程、线程、协程的区别
进程和线程的关系
- 进程中任意一线程崩溃都会导致整个进程崩溃
- 线程之间可以共享进程中的数据
- 当一个进程被关闭后,操作系统会回收进程占用的资源:当一个进程退出时,操作系统会回收该进程所申请的所有资源;即使其中任意线程因为操作不当导致内存泄漏,当进程退出时,这些内存也会被正确回收
- 进程之间的内容相互隔离,使
OS
中的进程互不干扰
进程和线程的区别
- 进程可以看作独立的应用,线程不能
- 进程是
cpu
资源分配的最小单位,是能拥有资源和独立运行的最小单位;线程是cpu
调度的最小单位,线程是建立在进程的基础上的一次程序运行单位,一个进程中可以有多个线程。 - 线程可以共享同一进程中的资源;进程必须通过协助进行通信
- 进程切换比线程切换开销大。线程切换不会引起进程的切换,但某个进程中的线程切换到另一个进程中的线程时,会引起进程切换
- 创建和撤销进程时,系统都要为其分配或回收资源
线程和协程的区别
- 一个线程可以多个协程,一个进程也可以单独拥有多个协程。
- 线程进程都是同步机制,而协程则是异步。
- 协程能保留上一次调用时的状态,每次过程重入时,就相当于进入上一次调用的状态。
- 线程是抢占式,而协程是非抢占式的,所以需要用户自己释放使用权来切换到其他协程,因此同一时间其实只有一个协程拥有运行权,相当于单线程的能力。协程并不是取代线程, 而且抽象于线程之上, 线程是被分割的
CPU
资源, 协程是组织好的代码流程, 协程需要线程来承载运行, 线程是协程的资源, 但协程不会直接使用线程, 协程直接利用的是执行器(Interceptor
), 执行器可以关联任意线程或线程池, 可以使当前线程, UI线程, 或新建新程.。 - 线程是协程的资源。协程通过
Interceptor
来间接使用线程这个资源。