React是如何实现快速响应的?

86 阅读3分钟

React 理念

最近准备开始学习 React 源码了,在学习 react 源码之前,首先了解了一下 React 的理念,引用官方的话是这样的:

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

正如官方理念所说,关键是实现快速响应,那么如何去实现快速响应呢?

在我们日常生活中,会导致快速响应不能良好实现的有两种情况:

  • 页面或应用需要大量的操作与计算;
  • 发送网络请求后,需要等待响应结果导致不能快速响应。

这两类情况出现的根本原因又可以归结于这两类情况:

  • CPU 限制;
  • IO(网络延迟)。

下面我们从这两个方面来看看 React 是怎么做的。

CPU 问题

复现

我们先看看下面这个 demo:

function App() {
  const len = 3000;
  return (
    <ul>
      {Array(len)
        .fill(0)
        .map((_, i) => (
          <li>{i}</li>
        ))}
    </ul>
  );
}

const rootEl = document.querySelector("#root");
ReactDOM.render(<App />, rootEl);

这个 demo 向浏览器视图中渲染了 3000 个li,并且是通过 JS 来实现的,我们知道,JS 可以操作 DOM,但是GUI渲染线程JS线程是互斥的。所以JS 脚本执行浏览器布局、绘制(GUI 线程)不能同时执行。

那么如果 JS 线程执行时间过长,会导致什么呢?GUi 无法进行浏览器的布局和绘制

解决

React是如何解决这一问题的呢?

答案是:在浏览器每一帧加载的时间中,预留一些时间给 JS 线程,React利用这部分时间更新组件(在 React源码 (opens new window)中,预留的初始时间是 5ms)。

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

这样的工作有一个专业术语,被称作为时间切片,这样 JS 的执行就会被拆分到每一帧所预留的执行时间中,而浏览器就有剩余的时间执行布局和绘制

总结

React 利用时间切片解决CPU的限制,关键解决方案则是:将 JS 的一次性更新变为了可以中断的异步更新。

IO 问题(网络延迟)

说明

网络延迟的问题相信大家都会在日常中体验到,这里就不做过多的复现。简单来说就是在点击触发了某一请求事件之后需要等待网络的请求结果,那么这一段时间如果过长则导致了不能快速响应

我们直接来看 React 是如何解决这一问题的。

解决

在点击某一按钮触发事件之后,先在当前页面停留一小段时间,这一小段时间用来请求数据。如果这一时间足够短,用户基本不会感知得到。如果请求时间超过了某一个范围,再去展示loading的效果。

如果我们一点击就展示loading ,那么即便时间很短,也会让用户感知到。

React 源码参考Suspense (opens new window)功能及配套的hook——useDeferredValue (opens new window)

总结

实际上,在源码内部为了支持这些特性,同样也是将同步的更新变为了可中断的异步更新