React 性能优化:异步渲染(时间切片&渲染挂起)

5,602 阅读3分钟

React 15 以及之前的版本有一个主要的问题 —— 虚拟 DOMdiff 操作是同步完成的。

这就意味着当页面上有大量 DOM 节点时,diff 的时间可能过长,从而导致交互卡顿,或者直接没有反馈。

异步渲染主要解决 React 在很多场景下的性能问题,是一个具有开创性的功能。

异步渲染分成了两个部分:时间分片(Time Slicing)和渲染挂起(Suspense)。

时间分片(Time Slicing)

我们知道 React 是将虚拟 Dom 和真实 Dom 做了映射,虚拟 Dom 的改变会自动触发真实 Dom 的更新,也就是说 React 是可以自行决定更新的条件(when)和方式(how)。

时间分片(Time Slicing)指的是一种将多个粒度小的任务放入一个时间切片(一帧)中执行的一种方案。当 React 运用上时间分片后,Dom 操作的优先级低于浏览器原生行为,例如鼠标键盘的输入,从而保证用户操作的流畅性:

  • React 在渲染(render)的时候,不会阻塞现在的线程 如果你的设备足够快,你会感觉渲染是同步的。
  • 如果你设备非常慢,你会感觉还算是灵敏的
  • 虽然是异步渲染,但是你将会看到完整的渲染,而不是一个组件一行行的渲染出来
  • 书写组件的方式不变

时间分片效果的实现还要依赖于 Chrome 的一个 API -- requestIdleCallback,该方法将在浏览器的空闲时段内调用函数。这使开发者能够在主事件循环上执行后台和低优先级工作,而不会影响延迟关键事件,如动画和输入响应。

我们可以看一下时间分片前后的效果对比(中间的表盘表示输入的延迟情况):

  • 同步渲染:感受浏览器原生事件的卡顿。

    ezgif.com-gif-maker.gif

  • 时间切片:优先保证浏览器原生行为的流畅

    ezgif.com-gif-maker (1).gif

渲染挂起(Suspense)

Suspense 使得组件可以“等待”某些操作结束后,再进行渲染。目前,Suspense 仅支持的使用场景是:通过 React.lazy 动态加载组件。它将在未来支持其它使用场景,如数据获取等。

  • React.lazy(): 允许你定义一个动态加载的组件。
  • React.Suspense: 可以指定加载指示器(loading indicator),以防其组件树中的某些子组件尚未具备渲染条件。

使用

  • pages/suspense/Lazy.js
const Lazy = () => {
  return <div>i am lazy component</div>;
};

export default Lazy;
  • pages/suspense/index.js
import React from 'react';

// 该组件是动态加载的
const LazyComponent = React.lazy(() => import('./Lazy'));

const Spinner = () => <div>i am Spinner...</div>;

const Suspense = () => {
  // 显示 <Spinner> 组件直至 OtherComponent 加载完
  return (
    <React.Suspense fallback={<Spinner />}>
      <div>
        <LazyComponent />
      </div>
    </React.Suspense>
  );
};

export default Suspense;

展示效果

官方帮助我们更优雅的管理 loading 状态。

ezgif.com-gif-maker (2).gif

参考资料

React 性能优化