
什么是Firber
- 一种数据结构:它可以用一个纯JS 对象来表示:
- 一个执行单元:每次执行完一个执行单元,React 就会检查现在还剩多少时间,如果没有时间就将控制权让出去。
Fiber关键特性:
- 增量渲染
- 不同更新的优先级
- 暂停,终止,复用渲染任务
- 并发方面新的基础能力
React Fiber 运行流程图
帧的概念
- 目前大多数设备的屏幕的刷新率为60次/秒, 即60帧每秒, 人眼舒适放松时可视帧数是每秒24帧 , 帧数 (fps) 越高,所显示的动作就会越流畅,小于这个值的时候,用户就会感觉到卡顿
- 所以对于现在主流屏幕设备来说,每个帧的预算时间就是1/60 约等于16.66毫秒
- 每个帧的开头包括样式计算、布局和绘制
- JavaScript执行Javascript引擎和页面渲染在同一个线程中,GUI渲染和Javascript执行两者之间是互斥的
- 如果某个任务执行时间过长,浏览器就会推迟渲染。
Fiber 结构示意图
window.requestAnimationFrame
window.requestAnimationFrame()
告诉浏览器——你希望执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画。该方法需要传入一个回调函数作为参数,该回调函数会在浏览器==下一次重绘之前执行==
注意:若你想在浏览器下次重绘之前继续更新下一帧动画,那么回调函数自身必须再次调用window.requestAnimationFrame()
当你准备更新动画时你应该调用此方法。这将使浏览器在下一次重绘之前调用你传入给该方法的动画函数(即你的回调函数)。回调函数执行次数通常是每秒60次,但在大多数遵循W3C建议的浏览器中,回调函数执行次数通常与浏览器屏幕刷新次数相匹配。为了提高性能和电池寿命,因此在大多数浏览器里,当requestAnimationFrame()
运行在后台标签页或者隐藏的iframe里时,requestAnimationFrame()
会被暂停调用以提升性能和电池寿命。
回调函数会被传入DOMHighResTimeStamp
参数,DOMHighResTimeStamp
指示当前被 requestAnimationFrame()
排序的回调函数被触发的时间。在同一个帧中的多个回调函数,它们每一个都会接受到一个相同的时间戳,即使在计算上一个回调函数的工作负载期间已经消耗了一些时间。该时间戳是一个十进制数,单位毫秒,最小精度为1ms(1000μs)。
请确保总是使用第一个参数(或其它获得当前时间的方法)计算每次调用之间的时间间隔,否则动画在高刷新率的屏幕中会运行得更快。请参考下面例子的做法。
1 | window.requestAnimationFrame(callback); |
参数
callback
下一次重绘之前更新动画帧所调用的函数(即上面所说的回调函数)。该回调函数会被传入
DOMHighResTimeStamp
参数,该参数与performance.now()
的返回值相同,它表示requestAnimationFrame()
开始去执行回调函数的时刻。
返回值
一个 long
整数,请求 ID ,是回调列表中唯一的标识。是个非零值,没别的意义。你可以传这个值给 window.cancelAnimationFrame()
以取消回调函数。
1 | const element = document.getElementById('some-element-you-want-to-animate'); |
requestIdleCallback
这是一个实验中的功能
`window.requestIdleCallback()`方法插入一个函数,这个函数将在浏览器空闲时期被调用。这使开发者能够在主事件循环上执行后台和低优先级工作,而不会影响延迟关键事件,如动画和输入响应。函数一般会按先进先调用的顺序执行,然而,如果回调函数指定了执行超时时间`timeout`,则有可能为了在超时前执行函数而打乱执行顺序。
你可以在空闲回调函数中调用requestIdleCallback()
,以便在下一次通过事件循环之前调度另一个回调。
强烈建议使用timeout
选项进行必要的工作,否则可能会在触发回调之前经过几秒钟。
1 | var handle = window.requestIdleCallback(callback[, options]) |
requestAnimationFrame的回调会在每一帧确定执行,属性高优先级任务,而requestIdleCallback的回调则不一定,属性低优先级的任务
返回值
一个ID,可以把它传入 Window.cancelIdleCallback()
方法来结束回调。
参数
callback
一个在事件循环空闲时即将被调用的函数的引用。函数会接收到一个名为
IdleDeadline
的参数,这个参数可以获取当前空闲时间以及回调是否在超时时间前已经执行的状态。options
可选包括可选的配置参数。具有如下属性:
timeout
: 如果指定了timeout,并且有一个正值,而回调在timeout毫秒过后还没有被调用,那么回调任务将放入事件循环中排队,即使这样做有可能对性能产生负面影响。
以下是用户对性能延迟的感知:
延迟时间 | 用户感知 |
---|---|
0-16ms | 很流畅 |
0-100ms | 基本流畅 |
100-1000ms | 感觉到网站上有一些加载任务 |
1000ms or more | 失去耐心了 |
10000ms or more | 直接离开,不会再访问了 |
事件处理最好在 50ms 内完成
MessageChannel
Channel Messaging API的**MessageChannel
** 接口允许我们创建一个新的消息通道,并通过它的两个MessagePort
属性发送数据。
目前requestIdleCallback只有chrome支持
所以React利用MessageChannel模拟了requestIdleCallback,将回调延迟到绘制操作之后执行
MessageChannel API 允许我们创建一个新的消息通道,并通过他的两个MessagePort属性发送数据
MessageChannel创建了一个通信的管道,这个管道有两个端口,每个端口都可以通过postMessage发送数据,而一个端口只要绑定了onmessage回调方法,就可以接受另外一个端口传过来的数据
MessageChannel是一个宏任务。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
let channel = new MessageChannel();
let port1 = channel.port1;
let port2 = channel.port2;
port1.onmessage = function(e){
console.log("接收到的数据:",e.data);
}
port2.onmessage = function(e){
console.log("接收到的数据:",e.data);
}
port1.postMessage("port1");
port2.postMessage("port2");
</script>
</body>
</html>
rafTime 从页面导航开始,这一帧开始的时间
performance.timing.navigationStart +performance.now() 约等于Date.now();
1 | let activeFrameTime = 1000/60;// 约等于 16.6 |
Fiber执行阶段
每次渲染有两个阶段:Reconciliation(协调render阶段)和Commit(提交阶段)
协调的阶段:可以认为是Diff阶段,这个阶段可以被终止,这个阶段会找出所有节点变更,例如节点新增、删除、属性变更等等,这些变更React称之为副作用。
提交阶段:将上一阶段计算出来的需要处理的副作用(effects)一次性执行了。这个阶段必须同步执行,不能被打断。
遍历规则
深度优先
模拟实现
- 工作单元
1 | let element = (<div id="A1"> |
- Post title: Fiber 原理
- Create time: 2022-07-23 13:15:00
- Post link: 2022/07/23/Fiber原理/
- Copyright notice: All articles in this blog are licensed under BY-NC-SA unless stating additionally.