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 (
<div
style={{ 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 来重新渲染列表。