RequestIdleCallback in Fiber
FPS 与页面流畅¶
- fps,short for Frames Per Second,即每一秒的帧数。
- fps 越大,浏览器越流畅。浏览器的 fps 一般等于屏幕的刷新率,不会超过屏幕的刷新率。
Frame 帧¶
浏览器 fps 通常为 60,每一帧执行完所有任务不能超过16.66ms(1000ms/60)。
一帧内需要完成如下六个步骤的任务:
- 处理用户的交互
- JS 解析执行
- 帧开始。窗口尺寸变更,页面滚去等的处理
- rAF
- 布局
- 绘制
RequestAnimationFrame¶
window.requestAnimationFrame() 会在浏览器重绘前调用传入的回调函数。
RequestIdleCallback¶
requestIdleCallback will schedule work when there is free time at the end of a frame, or when the user is inactive.
只有当一帧的末尾有空闲时间,才会执行回调函数。
window.requestIdleCallback(callback[, options])
callback
:回调即空闲时需要执行的任务,接收一个IdleDeadline
对象作为入参。- 只有当前帧的运行时间小于 16.66ms 时,callback 才会执行;否则,推迟到下一帧。以此类推。
- 其中
IdleDeadline
对象包含:didTimeout
,布尔值,表示任务是否超时,结合timeRemaining
使用。timeRemaining()
,表示当前帧剩余的时间,也可理解为留给任务的时间还有多少。
options
:目前 options 只有一个参数 timeout 。表示超过这个时间后,如果任务还没执行,则强制执行,不必等待空闲。
function sleep(delay) {
let _start = Date.now();
while(Date.now() - _start < delay) {}
}
// 任务队列
var start = Date.now();
var tasks = [
() => {
console.log("第1个任务开始");
sleep(1000);
console.log("第1个任务结束");
},
() => {
console.log("第2个任务开始");
sleep(300);
console.log("第2个任务结束");
console.log(Date.now() - start);
},
];
function work() {
tasks.shift()();
}
// 设置超时时间
var rIC = () => window.requestIdleCallback(runTask, { timeout: 1000 });
function runTask(deadline) {
if (
(
deadline.timeRemaining() > 0 ||
deadline.didTimeout
) &&
tasks.length > 0
) {
work();
}
if (tasks.length > 0) {
rIC();
}
}
rIC();
rIC vs rAF¶
- rIC short for requestIdleCallback,rAF short for requestAnimationFrame。
- rAF 的回调会在每一帧确定执行,属于高优先级任务;而 rIC 的回调则不一定,属于低优先级任务。
- 对于用户,不那么重要的任务放在 rIC 中执行,如 react 的运算及调度。
- 而对于用户体验比较重要的任务(如:动画、click 事件任务等)放在 rAF 回调中执行。
React Scheduler¶
// fiber tree
let A1 = { key: 'A1' };
let B1 = { key: 'B1', return: A1 };
let B2 = { key: 'B2', return: A1 };
let C1 = { key: 'C1', return: B1 };
let C2 = { key: 'C2', return: B1 };
A1.child = B1;
B1.child = C1;
B1.sibling = B2;
C1.sibling = C2;
// React scheduler
let workInProgressRoot = null;
let nextUnitOfWork = null; // 下一工作单元
function sheduleRoot() {
workInProgressRoot = A1;
nextUnitOfWork = workInProgressRoot;
}
function workLoop(deadline) {
let shoudYield = false; // 是否让出执行控制权
while(nextUnitOfWork && !shoudYield) {
nextUnitOfWork = performWorkofUnit(nextUnitOfWork);
shoudYield = deadline.timeRemaining() < 1;
}
if (!nextUnitOfWork && workInProgressRoot) {
// commitRoot(workInProgressRoot);
workInProgressRoot = null
}
// 进入死循环
requestIdleCallback(workLoop, { timeout: 500 });
}
function performWorkofUnit(fiber) {
beginWork(fiber);
if (fiber.child) {
return fiber.child;
}
while(fiber) {
completeUnitOfWork(fiber);
if (fiber.sibling) {
return fiber.sibling;
}
fiber = fiber.return;
}
}
requestIdleCallback(workLoop, { timeout: 500 });
function beginWork(fiber) {
console.log('beginWork:', fiber.key);
}
function completeUnitOfWork(fiber) {
console.log('completeUnitOfWork:', fiber.key);
}
// 每次渲染,执行一次 shedule
sheduleRoot();