React整体架构【面试题】

212 阅读3分钟

在19年React发布了16版本,这个版本最大的特点就是架构发生了改变。

由Stack架构更新为Fiber架构。来看一下两种架构的构成。

Stack架构

  • Reconciler(协调器):vdom 的实现,根据自变量的变化计算出 UI 的变化
  • Renderer(渲染器):负责将 UI 的变化渲染到宿主环境

协调器负责生成vDom,并进行diff。渲染器将vDom的更新渲染浏览器。

Stack架构的在每次更新时都会递归遍历vDom树,每一次递归都会开启一个函数栈所以叫Stack(栈)架构。

React官方还给了两种架构区别的例子

Stack例子

Fiber例子

从例子中可以看到Stack架构很卡,而Fiber却很丝滑。造成卡顿的原因还是由于浏览器的事件循环机制造成的,因为JS引擎只有一个主线程,同步代码,微任务,宏任务,dom操作。他们最终都会在主线程上被执行。比如setTimeout,会被先推入宏任务线程上,但是当timeout结束时,还是会将callback推入到主线程。

可以理解为主线程被的执行栈被塞满了,要一个个的排队执行。这也是定时器和计时器并不是100%准确的原因。

Fiber架构

  • Scheduler(调度器):调度任务的优先级,高优先级的任务会优先进入到 Reconciler
  • Reconciler(协调器):vdom 的实现,根据自变量的变化计算出 UI 的变化
  • Renderer(渲染器):负责将 UI 的变化渲染到宿主环境

同Stack架构相比多了一个调度器。在细节上v16还增加了Fiber Node,这是一种新的虚拟Dom。原来的虚拟Dom是树形结构,而Fiber Node则是链表结构。

虚拟Dom树

image.png

Fiber 链表

image.png

使用链表的结构结构最大的好处就是可以被打断。因为链表上的每个节点都可以被当做head。而且双向链表可以更方便的处理后续的complateWork工作。


我们知道屏幕的PFS大于等于60时我们视觉上感觉不到卡顿,也就是1000ms / 60 ≈ 160; 每0.16秒渲染一次。

浏览器的渲染流水线大概是

graph TD
requestAnimationFrame --> 解析HTML 
--> 计算样式 --> 布局 --> 更新图层 --> 
绘制像素点 --> requestIdleCallback

调度器会给每个任务打上tag,不同的任务有着不同的优先级。高优先级的任务会先进入协调器。

协调器就是根据requestIdleCallback判断在这一帧的时间切片是否还有足够的时间,如果时间不够了就去打断js的执行,在下一帧再接着去计算。

  function delay(duration) {
    const start = Date.now();
    while (Date.now() - start < duration) {}
  }

  const taskList = []; // 存放任务的队列

  // 推入任务
  for (let i = 1; i <= 10; i++) {
    taskList.push(() => {
      delay(10);
      console.log(`执行任务${i}`);
    });
  }
  function callback(IdleDeadline) {
    // 执行任务
    console.log(
      "当前帧绘制完毕后所剩余的时间:",
      IdleDeadline.timeRemaining()
    );
    while (IdleDeadline.timeRemaining() > 0 && taskList.length) {
      // 还有剩余时间,并且任务列表还有任务
      const task = taskList.shift();
      task();
    }
    // 退出上面的 while 后,有一种情况是当前帧的时间不够了,但是任务列表中还有剩余任务
    if (taskList.length) {
      // 那么我们就在下一帧空闲时间再继续执行任务
      window.requestIdleCallback(callback);
    }
 }

window.requestIdleCallback(callback);

渲染器将diff后的fiber一次性渲染,渲染是不可打断的。