北鸟南游的博客 北鸟南游的博客
首页
  • 前端文章

    • JavaScript
    • Nextjs
  • 界面

    • html
    • css
  • 计算机基础
  • 后端语言
  • linux
  • mysql
  • 工具类
  • 面试相关
  • 图形学入门
  • 入门算法
  • 极客专栏
  • 慕课专栏
  • 电影资源
  • 儿童动漫
  • 英文
关于我
归档
GitHub (opens new window)
首页
  • 前端文章

    • JavaScript
    • Nextjs
  • 界面

    • html
    • css
  • 计算机基础
  • 后端语言
  • linux
  • mysql
  • 工具类
  • 面试相关
  • 图形学入门
  • 入门算法
  • 极客专栏
  • 慕课专栏
  • 电影资源
  • 儿童动漫
  • 英文
关于我
归档
GitHub (opens new window)
  • JavaScript

    • 原生js
    • vue
    • react
      • react的fiber架构
        • 起因
          • rAF【requestAnimationFrame】回调函数
          • requestIdleCallback
          • MessageChannel
          • 模拟实现 原生的requestIdleCallback
        • Fiber是什么
          • Fiber是一个执行单元
          • Fiber是一种数据结构
        • Fiber执行阶段
          • render阶段
      • 从0实现vite+react的ssr服务端渲染
      • 基于React搭建threejs[最流行的3D库]环境
    • node
    • nextjs
    • 其它框架
  • 界面

  • front
  • javascript
  • react
北鸟南游
2022-10-22
目录

react的fiber架构

# react的fiber架构

  • JavaScript执行时,js引擎和页面渲染引擎在同一个渲染线程,GUI渲染和JavaScript执行两者互斥。
  • 如果js执行任务事件过程,则会影响浏览器的渲染引擎,造成页面卡顿。

# 起因

  • 目前主流设备的屏幕刷新率为60次/秒
  • 每秒绘制的帧数(FPS)达到 60 页面显示才流畅;小于这个值时,用户会感觉到卡顿;
  • 每帧的预算时间是16.667ms
  • 每帧的开始包括样式计算、布局、绘制
  • JavaScript执行和页面渲染在同一个渲染进程 (opens new window)中,GUI渲染和JavaScript执行两者互斥
  • 如果js任务执行时间过 长,浏览器渲染会被推迟。

浏览器一秒渲染60帧画面,每帧显示时间在16.66ms。浏览器在绘制每帧时经历的过程,如下图: Frame生命周期.png

# rAF【requestAnimationFrame】回调函数

  • requestAnimationFrame回调函数在绘制之前执行。
  • 每帧绘制时都会执行
  • requestAnimationFrame(callback) 的 callback回调函数中的参数timestamp是回调被调用的时间,也就是当前帧的起始时间
  • rafTime 等于 performance.timing.navigationStart + performance.now ; 约等于Date.now();
<body>
<div id="box"
 style="background-color: red;width: 1px; height: 20px;color: antiquewhite;"
></div>
<button>start</button>
<script>
  let div = document.querySelector("#box");
  let button = document.querySelector("button");
  let startTime;
  function progress() {
    div.style.width = div.offsetWidth + 1 + "px";
    div.innerHTML = div.offsetWidth + "%";
    if (div.offsetWidth < 100) {
      startTime = Date.now();
      requestAnimationFrame(progress);
    }
  }
  button.onclick = function () {
    div.style.width = 0;
    startTime = Date.now();
    // 点击按钮时,调用一次
    requestAnimationFrame(progress);
  };
</script>
</body>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

# requestIdleCallback

  • requestIdleCallback 在每帧绘制完成后如果有剩余时间才执行,而不影响用户交互的关键事件,如动画、窗口缩放和输入响应
  • 正常一帧任务完成后,时间小于16.6ms还有剩余时间,就会执行 requestIdleCallback 里面的任务。
  • requestAnimationFrame 的回调在每帧时必定执行,属于高优先级任务;requestIdleCallback 回调则不能保证每帧都执行,属于低优先级任务。

image.png

function sleep(delay) {
  for (let now = Date.now(); Date.now() - now < delay; ) {}
}
let allStart = 0;
const works = [
  () => {
    allStart = Date.now();
    console.log("this first work start");
      sleep(20);
    console.log("this first work end");
  },
  () => {
    console.log("this second work start");
      sleep(15);
    console.log("this second work end");
  },
  () => {
    console.log("this third work start");
      sleep(10);
    console.log("this third work end");
    console.log(Date.now() - allStart);
  },
];

requestIdleCallback(workLoop, { timeout: 1000 });
// deadline参数是一个对象,有2个属性
//  timeRemaining(), 返回此帧还剩下多少时间让用户使用
//  didTimeout 判断回调任务callback是否超时
function workLoop(deadline) {
  console.log(`this frame remainTime is ${deadline.timeRemaining()}`);
  // deadline.timeRemaining() > 0 说明还有剩余时间
  // deadline.didTimeout说明任务已经过期,则必须执行
  while (
    (deadline.timeRemaining() > 0 || deadline.didTimeout) &&
    works.length > 0
  ) {
    performUnitOfWork();
  }
  if (works.length > 0) {
    console.log(`this frame remainTime is ${deadline.timeRemaining()},时间片已经到期,等待下次调度`);
    window.requestIdleCallback(workLoop, { timeout: 1000 });
  }
}
function performUnitOfWork() {
  works.shift()();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46

浏览器事件执行.png

# MessageChannel

  • 上面的 requestIdleCallback 方法只有Chrome支持
  • react利用 MessageChannel 模拟 requestIdleCallback ,将回调延迟到绘制操作之后
  • MessageChannel 允许创建一个新的消息通道,并通过它的2个 MessagePort 属性发送数据
  • MessageChannel 创建一个通信管道,这个管道有2个端口,每个端口都可以通过 postMessage 发送数据,另一个端口只要绑定了onmessage回调方法,就可以通过另一个端口传递数据
  • MessageChannel 是宏任务
<script>
  let channel = new MessageChannel();
  let port1 = channel.port1;
  let port2 = channel.port2;
  port1.onmessage = function (event) {
    console.log("port1接收的数据:", event.data);
  };
  port2.onmessage = function (event) {
    console.log("port2接收的数据:", event.data);
  };
  port1.postMessage('port1发送数据');
  port2.postMessage('port2发送数据');
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13

# 模拟实现 原生的requestIdleCallback

let channel = new MessageChannel();
let activeFrameTime = 1000 / 60;
let frameDeadline; // 每帧的截止时间
let pendingCallback;
let timeRemaining = () => frameDeadline - performance.now();
channel.port2.onmessage = function () {
  let currentTime = performance.now();
  // 如果帧的截止时间,小于当前时间,说明已经过期
  let didTimeout = frameDeadline <= currentTime;
  if (didTimeout || timeRemaining() > 0) {
    if (pendingCallback) {
      // 原生requestIdleCallback回调函数中的2个参数
      pendingCallback({ didTimeout, timeRemaining });
    }
  }
};
window.requestIdleCallback = (callback, options) => {
  requestAnimationFrame((rafTime) => {
    // rafTime帧的开始时间
    console.log("rafTime", rafTime);
    frameDeadline = rafTime + performance.now();
    pendingCallback = callback;
    channel.port1.postMessage("send");
  });
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

# Fiber是什么

  • 通过某些调度策略合理分配CPU资源,提高用户的响应速度。
  • 通过 Fiber 架构,react把协调组件数据变化的过程变成可被中断,在适当的时候让出GPU执行权,就可以让浏览器及时处理用户的交互。

# Fiber是一个执行单元

  • Fiber是一个执行单元,react在检查现在还剩下多少时间,如果没有时间交出控制权。

image.png

# Fiber是一种数据结构

  • react目前的做法使用链表,每个虚拟节点内部表示为Fiber.

image.png

# Fiber执行阶段

每个渲染节点有2个阶段:Reconciliation(协调render阶段)和Commit(提交阶段)

  • Reconciliation阶段:可以认为是diff过程,这个阶段可以被中断。在该阶段找出所有节点变更,例如节点新增、删除、属性变更等,这些称为react的 副作用(effect)
  • Commit阶段:将上个阶段计算出来的需要处理的副作用一次执行。这个阶段的执行必须同步执行,不能被打断。

# render阶段

  • 从顶点开始遍历
  • 如果有儿子,先遍历第一个儿子
  • 如果没有儿子,标志着此节点遍历完成
  • 如果有兄弟节点,继续遍历兄弟节点
  • 如果没有下一个兄弟,返回父节点并标识 父节点遍历完成;如果有叔叔节点,则遍历叔叔节点;没有叔叔节点,则父节点上一个节点遍历结束。整个流程遍历完成

image.png

/* 
Fiber是一个执行单元
    1. Fiber是一个执行单元,类似对象。每次执行完一个执行单元,react会检查可使用的时间还有多少,如果没时间就把控制权让出去
    2. 通过Fiber架构,让Reconcilation过程变成可被中断,适时会让出CPU执行权,让浏览器优先处理用户的交互
Fiber执行阶段
    每次渲染包含2个阶段:Reconcilation(协调或render渲染)和Commit(提交阶段)
    协调阶段:Reconcilation可以认为是diff阶段,该阶段可被中断,会造成节点变更,如新增、删除等,这些变更react称为副作用effect
    提交阶段:将上一个阶段计算出来的所有需要处理的副作用一次性执行。这个阶段必须同步执行,不能被打断
 */
let A1 = { type: "div", key: "A1" };
let B1 = { type: "div", key: "B1", return: A1 };
let B2 = { type: "div", key: "B2", return: A1 };
let C1 = { type: "div", key: "C1", return: B1 };
let C2 = { type: "div", key: "C2", return: B1 };
A1.child = B1;
B1.sibling = B2;
B1.child = C1;
C1.sibling = C2;

let nextUnitOfWork; // 下一个执行单元
function workLoop() {
  while (nextUnitOfWork) {
    nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
  }
  if (!nextUnitOfWork) {
    console.log("render阶段结束");
  }
}
function performUnitOfWork(fiber) {
  beginWork(fiber);
  if (fiber.child) {
    //如果有子,返回第一个子节点 A1之后B1
    return fiber.child;
  }
  while (fiber) { // 如果没有子,说明该节点完成。
    completeUnitOfWork(fiber);
    // 节点自身完成,开始查看是否有兄弟节点,如果有返回兄弟节点
    if (fiber.sibling) {
      return fiber.sibling;
    }
    // 兄弟节点也遍历完毕,则返回父节点,让父节点再次查找兄弟节点,即该节点的叔叔节点。
    fiber = fiber.return;
  }
}
function completeUnitOfWork(fiber) {
  console.log(fiber.key, "end");
}
function beginWork(fiber) {
  console.log(fiber.key, "start");
}
nextUnitOfWork = A1;
workLoop();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
编辑 (opens new window)
上次更新: 2025/04/19, 14:22:11
vue响应数据
从0实现vite+react的ssr服务端渲染

← vue响应数据 从0实现vite+react的ssr服务端渲染→

最近更新
01
色戒2007
04-19
02
真实real
04-19
03
Home
更多文章>
Theme by Vdoing | Copyright © 2018-2025 北鸟南游
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式