React-Fiber原理

在 React 16 之前,更新虚拟 DOM 的过程称为 Stack Reconciler,这是一个递归的过程,在层级很深的时候,单次 diff 时间过长会让 JS 线程持续被占用,导致用户交互响应迟滞,页面渲染会出现明显的卡顿。为了解决这种问题,react 团队基于 Fiber 架构重构了 React,使其能够将任务分片,划分优先级,同时能够实现类似于操作系统中对线程的抢占式调度

写在前面

由于主流的屏幕刷新率都在 60hz,因此浏览器渲染一帧的时间必须控制在 16.7ms 内才能保证不掉帧,也就是说每一次渲染都要控制在 16.7ms 内,页面才不会卡顿

浏览器每一帧都需要完成哪些工作?
浏览器一帧内的工作.png

通过上图可看到,一帧内需要完成如下六个步骤的任务:

  1. 处理用户交互
  2. 解析 js 脚本
  3. Begin frame。resize、scroll 等事件的处理
  4. rAF
  5. 布局
  6. 绘制

页面是一帧一帧绘制出来的,当 FPS(每秒绘制的帧数)达到 60 时,页面是流畅的,小于这个值时,用户会感觉到卡顿。也就是说浏览器 1s 绘制 60 帧,每一帧分到的时间是 1000/60 ≈ 16 ms。所以我们应该力求不让一帧的工作量超过 16ms。

由此我们知道,浏览器是一帧一帧执行的,在两个执行帧之间,主线程通常会有一小段空闲时间

浏览器一帧工作的空余.png

requestIdleCallback 可以在这个空闲期(Idle Period)调用空闲期回调(Idle Callback),执行一些任务

Fiber reconciler

Fiber 协调核心:可中断可恢复优先级

新特性如下:

  • 给不同类型的更新任务赋予优先级,能够暂停、终止和复用渲染任务
  • 把渲染任务拆分成块,匀到多帧
  • 并行渲染(还在开发中,值得期待)

将以前的 stack reconciler 拆分成两个阶段:rendercommit。render 阶段是可打断的,被拆分成一个个的小任务(fiber),在每一侦的渲染空闲期执行。然后是 commit 阶段,这个阶段是不拆分且不能打断的,将最终的 effectList 一口气更新到页面上。这两个阶段后续会详细介绍

  • 每个更新任务都会赋予一个优先级
  • 当更新任务抵达调度器时,高优先级的更新任务(A)会更快地被调度进入 Reconciler —— 优先级
  • 此时有新的更新任务(B),调度器会检查它优先级,若高于当前任务(A),处于当前 Reconciler 层的 A 任务会被中断,调度器将 B 任务推入 Reconciler 层 —— 可中断
  • 当 B 任务完成渲染后,新一轮调度开始,之前被中断的 A 任务将会被重新推入 Reconciler 层,继续它的渲染 —— 可恢复

Fiber Node

React Fiber 把更新过程碎片化,以 Fiber Node 为一个工作单元,执行过程如下面的图所示,每执行完一段更新过程,就把控制权交还给 React 负责任务协调的模块,看看有没有其他紧急任务要做,如果没有就继续去更新,如果有紧急任务,那就去做紧急任务

有了分片之后,更新过程的调用栈如下图所示,中间每一个波谷代表深入某个分片的执行过程,每个波峰就是一个分片执行结束交还控制权的时机,让线程处理别的事情

fiber调度.png

一个虚拟 DOM 节点对应一个 Fiber Node

type Fiber = {
  // 标识不同的组件类型
  // FunctionComponent: 0; ClassComponent: 1...
  tag: WorkTag,

  key: null | string,

  // react 元素类型
  // 可以为ClassComponent、FunctionComponent、Symbol、HostComponent...
  elementType: any,

  // The resolved function/class/ associated with this fiber.
  type: any,

  // 该fiber节点对应的ReactElement对象
  stateNode: any,

  // fiber的父级
  return: Fiber | null,

  // 单链表树状结构
  child: Fiber | null,
  sibling: Fiber | null,
  index: number,

  ref: null | (((handle: mixed) => void) & { _stringRef: ?string }) | RefObject,

  // 新 props
  pendingProps: any,
  // 旧 props
  memoizedProps: any,

  // 更新队列
  updateQueue: UpdateQueue<any> | null,

  // 旧 state
  memoizedState: any,

  // DOM diff相关
  effectTag: SideEffectTag,

  // 副作用链表
  nextEffect: Fiber | null,
  firstEffect: Fiber | null,
  lastEffect: Fiber | null,

  // 标识该组件应该在未来的某个时刻完成更新
  // expirationTime 五种类型:
  // NoWork(默认0)、Never(1)、Interactive(100 - 150)、Async(250 - 5000)、Sync(Number.MAXINTEGER)
  expirationTime: ExpirationTime,

  // 快速确定子fiber树有没有挂起的更改
  childExpirationTime: ExpirationTime,

  // fiber 的镜像节点
  alternate: Fiber | null,
};

Fiber 树示意图如下:

fiber树结构.png

演示:

创建fiber.gif

双链表

在 react 中始终存在 workInprogressTree(future vdom)oldTree(current vdom)两个链表,两个链表相互引用。

fiber双链表

双链表的好处有:

  • 复用内部对象(Fiber Node),可以节省内存分配、GC 的时间开销
  • 获取旧状态
  • 当 workInprogressTree 生成报错时,这时也不会导致页面渲染崩溃,而只是更新失败,页面仍然还在

最后

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值