campcc/blog

React设计理念

Opened this issue · 0 comments

image

全文约 1500 字,阅读完大约需要 6 分钟,读完本文,你可以 get 到,

  • React 的设计理念是什么?
  • 有哪些因素可以制约网页的快速响应?
  • 什么是 CPU 的瓶颈?
  • 什么是 IO 瓶颈?
  • 什么是掉帧?
  • React 如何将人机交互研究的结果整合到真实的 UI 中?
  • React 引入新 Fiber 架构的原因?

正式开始之前,建议先阅读官网的 React 哲学 一文 ,

假设你已经阅读完 React 哲学,文中提到,

React 是用 JavaScript 构建快速响应的大型 Web 应用程序的首选方式,它在 Facebook 和 Instagram 上表现优秀。

可见 快速响应 是 React 设计中至关重要的一个理念,那么如何实现 快速响应 呢?

这里我们可以从相反的角度考虑,制约快速响应的因素有哪些?

一般来说,网页的响应速度受以下两类场景的约束,

  • 遇到大计算量的操作或者设备性能不足导致的页面掉帧,卡顿
  • 等待网络请求数据返回才能进一步操作导致的 IO 耗时

这两类场景可以概括为,

  • CPU的瓶颈
  • IO 的瓶颈

React 是如何解决这两个瓶颈的呢?

CPU的瓶颈

要解决 CPU 瓶颈,首先要明白什么是 “掉帧” 。

我们知道主流浏览器的刷新频率为 60Hz(1000ms / 60Hz),即每 16.6ms 刷新一次,而 Javascript 是可以操作 DOM 的,为了避免无法预期的渲染结果,在浏览器的架构设计上,渲染进程和 JS 引擎是互斥的,所以当一帧内 Javascript 脚本执行的时间过长(超过了浏览器的刷新时间 16.6ms)时,本次刷新就没有时间去执行样式布局样式绘制了,也就导致了所谓的 “掉帧”。

比如下面的堆栈图打印,我们可以看到 Javascript 的执行时间为 73.65ms,远多于一帧的时间,

明白了原理,解决这个问题就很简单了,我们需要在浏览器每一帧的时间中,预留一部分时间给 JS 线程,然后把控制权交还给渲染进程,让浏览器有剩余时间去执行样式布局和绘制。

源码里,预留的时间是 5ms,

// Scheduler periodically yields in case there is other work on the main
// thread, like user events. By default, it yields multiple times per frame.
// It does not attempt to align with frame boundaries, since most tasks don't
// need to be frame aligned; for those that do, use requestAnimationFrame.
let yieldInterval = 5;

当预留时间不够时,React 将控制权交还给渲染进程使其有时间来渲染 UI,React 则等待下一帧时间到来继续被中断的工作。

这种将长任务分拆到每一帧中,像蚂蚁搬家一样一次执行一小段任务的操作,叫做时间切片(Time Slice),这与进程和网络调度程序常用的 时间片轮转调度 (Round-Robin Scheduling) 算法类似,这种方式的缺点是任务运行的总时间变长了,但可以明显减少掉帧的可能性,对于浏览器渲染而言,这种取舍是有必要的,

所以解决 CPU 瓶颈的关键是 时间切片,而时间切片的关键是:将同步更新变为可中断的异步更新

IO的瓶颈

前面我们介绍到,IO 的瓶颈主要来源于 网络延迟,但实际上,网络延迟 是前端开发者无法解决的。

如何在网络延迟客观存在的前提下,减少用户对延迟的感知呢?

我们可以在目前人机交互最顶尖的苹果(IOS)系统中找到一些灵感,以设置面板中的“通用”和“Siri与搜索”为例,

这里的点击“通用”的交互是同步的,而点击“Siri与搜索”是异步的,但从用户感知上看,两者的区别微乎其微,怎么做到的?

仔细观察我们发现,点击“Siri与搜索”后,系统在当前页面停留了一小段时间,这一小段时间被用来请求数据,当“这一小段时间”足够短时,用户是无感知的,如果请求时间超过一个范围,再显示 loading 的效果。这其实是人机交互研究的一个结果,通过类似的方案,可以明显改善用户对网络延迟的感知。

React 借鉴并应用了这一研究成果,实现了 Suspense 及配套的 useDeferredValue,将人机交互研究的结果整合到真实的 UI 中。

而在源码内部,为了支持这些特性,同样需要将同步的更新变为可中断的异步更新

设计理念

综上,我们不难发现 React 为了践行 “构建快速响应的大型 Web 应用程序” 做出的努力,其设计理念可以简单概括为快速响应

快速响应的关键在于解决 CPU 和 IO 瓶颈,

具体实现上,需要将同步的更新变为可中断的异步更新,这也是为什么 React 需要引入新的 Fiber 架构的原因。

写在最后

本文首发于我的 博客,才疏学浅,难免有错误,文章有误之处还望不吝指正!

如果有疑问或者发现错误,可以在评论区进行提问和勘误,

如果喜欢或者有所启发,欢迎 star,对作者也是一种鼓励。