1、时间分片
时间分片主要解决,初次加载,一次性渲染大量数据造成的卡顿现象。浏览器执 js 速度要比渲染 DOM 速度快的多。时间分片,并没有本质减少浏览器的工作量,而是把一次性任务分割开来;
效果:
代码实现:
import React, { useState } from "react";/* 获取随机颜色 */function getColor() {const r = Math.floor(Math.random() * 255);const g = Math.floor(Math.random() * 255);const b = Math.floor(Math.random() * 255);return "rgba(" + r + "," + g + "," + b + ",0.8)";}/* 获取随机位置 */function getPostion(position) {const { width, height } = position;return {left: Math.ceil(Math.random() * width) + "px",top: Math.ceil(Math.random() * height) + "px",};}/* 色块组件 */function Circle({ position }) {const style = React.useMemo(() => {//用useMemo缓存,计算出来的随机位置和色值。return {background: getColor(),position: "absolute",width: "10px",height: "10px",...getPostion(position),};}, [position]);return <div style={style} className="circle" />;}class Index extends React.Component {state = {dataList: [], //数据源列表renderList: [], //渲染列表position: { width: 0, height: 0 }, // 位置信息eachRenderNum: 500, // 每次渲染数量};box = React.createRef();componentDidMount() {const { offsetHeight, offsetWidth } = this.box.current;const originList = new Array(20000).fill(1);const times = Math.ceil(originList.length / this.state.eachRenderNum); /* 计算需要渲染此次数*/let index = 1;this.setState({dataList: originList,position: { height: offsetHeight, width: offsetWidth },},() => {this.toRenderList(index, times);});}toRenderList = (index, times) => {if (index > times) return; /* 如果渲染完成,那么退出 */const { renderList } = this.state;renderList.push(this.renderNewList(index)); /* 通过缓存element把所有渲染完成的list缓存下来,下一次更新,直接跳过渲染 */this.setState({renderList,});requestIdleCallback(() => {/* 用 requestIdleCallback 代替 setTimeout 浏览器空闲执行下一批渲染 */this.toRenderList(++index, times);});};renderNewList(index) {/* 得到最新的渲染列表 */const { dataList, position, eachRenderNum } = this.state;const list = dataList.slice((index - 1) * eachRenderNum,index * eachRenderNum);return (<React.Fragment key={index}>{list.map((_, index) => (<Circle key={index} position={position} />))}</React.Fragment>);}render() {return (<divstyle={{ width: "100%", height: "100vh", position: "relative" }}ref={this.box}>{this.state.renderList}</div>);}}export default () => {const [show, setShow] = useState(false);const [btnShow, setBtnShow] = useState(true);const handleClick = () => {setBtnShow(false);setTimeout(() => {setShow(true);}, 0);};return (<div>{btnShow && <button onClick={handleClick}>展示效果</button>}{show && <Index />}</div>);};
2、虚拟列表
处理大量Dom存在 带来的性能问题;
实际包含三个区域:
- 视图区:视图区就是能够直观看到的列表区,此时的元素都是真实的 DOM 元素。
- 缓冲区:缓冲区是为了防止用户上滑或者下滑过程中,出现白屏等效果。(缓冲区和视图区为渲染真实的 DOM )
- 虚拟区:剩下的区域,不需要渲染真实的 DOM 元素。

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