React 15 以及之前的版本有一个主要的问题 —— 虚拟 DOM 的 diff 操作是同步完成的。
这就意味着当页面上有大量 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,该方法将在浏览器的空闲时段内调用函数。这使开发者能够在主事件循环上执行后台和低优先级工作,而不会影响延迟关键事件,如动画和输入响应。
我们可以看一下时间分片前后的效果对比(中间的表盘表示输入的延迟情况):
-
同步渲染:感受浏览器原生事件的卡顿。
-
时间切片:优先保证浏览器原生行为的流畅
渲染挂起(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 状态。
参考资料
- React.lazy:zh-hans.reactjs.org/docs/react-…
- React.Suspense:zh-hans.reactjs.org/docs/react-…