React fiber--时间分片

3,552 阅读5分钟

fiber其实是一个多进程并发的过程。

这里涉及到并发和并行的区别

并发:操作系统按照一定的调度策略,将CPU的执行权分配给多个进程,让多个进程交替执行,由于时间很快,给用户一种同时在执行多种任务的错觉。实际上单核的CPU的物理环境下只能同时执行一个任务。

并行:与并发相反,是多核的物理环境的支持下,才能实现并行,也就是同时执行多个进程。

🔴并行可以是并发,而并发不一定是并行,两种不能划等号, 并行一般需要物理层面的支持

类比js的执行环境

js是单线程的,也就是说一次只能执行一个任务,并且在浏览器环境中,js线程和GUI渲染线程是不能同时执行的,js执行如果占用太多时间,就会阻碍浏览器的渲染,就会造成页面的卡顿、用户交互缓慢等情况。

因此,js存在一个缺陷,就是前面的同步任务会影响后面的其他任务的执行,如果开发者埋下一颗炸弹,对于浏览器和用户的交互体验来说,就会产生极差的影响。

对于框架来说,React选择了一种方式来解决这个问题。

快速响应用户,让用户觉得够快,不能阻塞用户的交互。

类似于CPU中的多进程并发,React用fiber的方式实现了多线程’并发‘。

React怎么做的?

React的协调过程:当需要更新某个状态时,React会对比两个虚拟DOM树,找出变化的节点,然后同步更新DOM,这个过程时同步执行的,不给浏览器一丝喘息的机会。

结果:无法响应用户的交互,页面卡顿效果明显。

同步模式下的 React:

16deecc3acaf5689_tplv-t2oaga2asx-zoom-in-crop-mark_4536_0_0_0.awebp

改进后模式下的React:

16deecc385cc0286_tplv-t2oaga2asx-zoom-in-crop-mark_4536_0_0_0.awebp

原理:

1.未改进之前的react的协调过程是通过递归来比对两个虚拟DOM树,然后更新DOM,整个递归的过程不可中断,并且中断后需要重新比对,无法记住当前的执行位置,因此在执行的过程中,无法交出执行的控制权。

2.🔴通过fiber架构改进后的react,是通过链表的形式比对虚拟DOM树,由于链表特殊的结构,可以随时中断执行,然后将执行权交给浏览器,用来响应用户交互和浏览器渲染,从而得到更好的用户体验。

fiber是什么

1.协程

类似于generator,是一种让程序停止,并交出程序控制权的流程控制机制。

const tasks = []
function * run() {
  let task

  while (task = tasks.shift()) {
    // 🔴 判断是否有高优先级事件需要处理, 有的话让出控制权 相当于执行异步任务
    if (hasHighPriorityEvent()) {
      yield
    }

    // 处理完高优先级事件后,恢复函数调用栈,继续执行...
    execute(task)
  }
}

解答:

由于浏览器不存在抢占式调度,只能使用协作式调度,即react向浏览器申请时间片来执行自己的任务单元,浏览器每帧(16ms)可能会执行以下任务:

  • 处理用户输入事件
  • Javascript执行
  • requestAnimation 调用
  • 布局 Layout
  • 绘制 Paint

当每一帧执行完以上程序后,会在空闲的时候或者react时间片过时之后,将控制权转交给react,react执行任务单元并计算剩余的时间,如果时间不足就交回控制权。

2.一个执行单元

将它视作一个执行单元,每次执行完一个'执行单元', React 就会检查现在还剩多少时间,如果没有时间就将控制权让出去.

React的改造

1.fiber架构

之前的react虚拟DOM的比较可以看作是一个栈调用,中间无法中断,无法异步执行。

现在的react虚拟DOM的比较是模拟栈调用而实现的链表调用,将递归的过程转换成迭代。

以下是表示一个虚拟dom节点的fiber结构:

export type Fiber = {
  // Fiber 类型信息
  type: any,
  // ...

  // ⚛️ 链表结构
  // 指向父节点,或者render该节点的组件
  return: Fiber | null,
  // 指向第一个子节点
  child: Fiber | null,
  // 指向下一个兄弟节点
  sibling: Fiber | null,
}

image.png

由于链表的特殊性,就算任务中断,下次再获取到执行权的时候,仍然可以继续执行。

2.分为两个阶段

协调

协调就是diff的过程,允许中断,会找出所有变化的节点、属性和删除、新增的节点等,并打上tag。

以下生命周期是在协调阶段执行的

  • constructor

  • static getDerivedStateFromProps

  • shouldComponentUpdate

  • render

提交

也就是将所有打标的虚拟dom进行更新的过程,不可中断。以下生命周期将在这个阶段执行:

  • componentDidMount

  • componentDidUpdate

  • componentWillUnmount

为什么要区分两个阶段?

更新DOM不可被中断。

假如有多个组件需要渲染的时候,render和构造函数可以被分成多个任务,异步执行,直到所有组件都被渲染完 毕。

在dom更新阶段,如果可以中断的话,会存在部分更新的情况,导致更新视图不一致。

所以,协调阶段是可以中断的,但是提交阶段不行。

一些放在componentDidMount中执行的副作用也不会因为中断而被执行两遍。

总结:fiber作为一种时间分片技术,通过与浏览器的合作式调度,很大程度上解决了js单线程造成的react更新时间过长引起的用户体验差的问题,使得react实现了‘实时’响应用户操作的功能。

image.png

参考文章:

这可能是最通俗的 React Fiber(时间分片) 打开方式(juejin.cn/post/684490…)