Skip to content

React设计理念 #44

@campcc

Description

@campcc

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,对作者也是一种鼓励。

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions