1、时间分片

时间分片主要解决,初次加载,一次性渲染大量数据造成的卡顿现象。浏览器执 js 速度要比渲染 DOM 速度快的多。时间分片,并没有本质减少浏览器的工作量,而是把一次性任务分割开来;

效果:
2021 (1).gif

代码实现:

  1. import React, { useState } from "react";
  2. /* 获取随机颜色 */
  3. function getColor() {
  4. const r = Math.floor(Math.random() * 255);
  5. const g = Math.floor(Math.random() * 255);
  6. const b = Math.floor(Math.random() * 255);
  7. return "rgba(" + r + "," + g + "," + b + ",0.8)";
  8. }
  9. /* 获取随机位置 */
  10. function getPostion(position) {
  11. const { width, height } = position;
  12. return {
  13. left: Math.ceil(Math.random() * width) + "px",
  14. top: Math.ceil(Math.random() * height) + "px",
  15. };
  16. }
  17. /* 色块组件 */
  18. function Circle({ position }) {
  19. const style = React.useMemo(() => {
  20. //用useMemo缓存,计算出来的随机位置和色值。
  21. return {
  22. background: getColor(),
  23. position: "absolute",
  24. width: "10px",
  25. height: "10px",
  26. ...getPostion(position),
  27. };
  28. }, [position]);
  29. return <div style={style} className="circle" />;
  30. }
  31. class Index extends React.Component {
  32. state = {
  33. dataList: [], //数据源列表
  34. renderList: [], //渲染列表
  35. position: { width: 0, height: 0 }, // 位置信息
  36. eachRenderNum: 500, // 每次渲染数量
  37. };
  38. box = React.createRef();
  39. componentDidMount() {
  40. const { offsetHeight, offsetWidth } = this.box.current;
  41. const originList = new Array(20000).fill(1);
  42. const times = Math.ceil(
  43. originList.length / this.state.eachRenderNum
  44. ); /* 计算需要渲染此次数*/
  45. let index = 1;
  46. this.setState(
  47. {
  48. dataList: originList,
  49. position: { height: offsetHeight, width: offsetWidth },
  50. },
  51. () => {
  52. this.toRenderList(index, times);
  53. }
  54. );
  55. }
  56. toRenderList = (index, times) => {
  57. if (index > times) return; /* 如果渲染完成,那么退出 */
  58. const { renderList } = this.state;
  59. renderList.push(
  60. this.renderNewList(index)
  61. ); /* 通过缓存element把所有渲染完成的list缓存下来,下一次更新,直接跳过渲染 */
  62. this.setState({
  63. renderList,
  64. });
  65. requestIdleCallback(() => {
  66. /* 用 requestIdleCallback 代替 setTimeout 浏览器空闲执行下一批渲染 */
  67. this.toRenderList(++index, times);
  68. });
  69. };
  70. renderNewList(index) {
  71. /* 得到最新的渲染列表 */
  72. const { dataList, position, eachRenderNum } = this.state;
  73. const list = dataList.slice(
  74. (index - 1) * eachRenderNum,
  75. index * eachRenderNum
  76. );
  77. return (
  78. <React.Fragment key={index}>
  79. {list.map((_, index) => (
  80. <Circle key={index} position={position} />
  81. ))}
  82. </React.Fragment>
  83. );
  84. }
  85. render() {
  86. return (
  87. <div
  88. style={{ width: "100%", height: "100vh", position: "relative" }}
  89. ref={this.box}
  90. >
  91. {this.state.renderList}
  92. </div>
  93. );
  94. }
  95. }
  96. export default () => {
  97. const [show, setShow] = useState(false);
  98. const [btnShow, setBtnShow] = useState(true);
  99. const handleClick = () => {
  100. setBtnShow(false);
  101. setTimeout(() => {
  102. setShow(true);
  103. }, 0);
  104. };
  105. return (
  106. <div>
  107. {btnShow && <button onClick={handleClick}>展示效果</button>}
  108. {show && <Index />}
  109. </div>
  110. );
  111. };

2、虚拟列表

处理大量Dom存在 带来的性能问题;

实际包含三个区域:

  • 视图区:视图区就是能够直观看到的列表区,此时的元素都是真实的 DOM 元素。
  • 缓冲区:缓冲区是为了防止用户上滑或者下滑过程中,出现白屏等效果。(缓冲区和视图区为渲染真实的 DOM )
  • 虚拟区:剩下的区域,不需要渲染真实的 DOM 元素。

image.png

思路:

  • 通过 useRef 获取元素,缓存变量。
  • useEffect 初始化计算容器的高度。截取初始化列表长度。这里需要 div 占位,撑起滚动条。
  • 通过监听滚动容器的 onScroll 事件,根据 scrollTop 来计算渲染区域向上偏移量, 这里需要注意的是,当用户向下滑动的时候,为了渲染区域,能在可视区域内,可视区域要向上滚动;当用户向上滑动的时候,可视区域要向下滚动。
  • 通过重新计算 end 和 start 来重新渲染列表。