Commit 39525ada authored by 刘磊's avatar 刘磊

update

parent 88c32d09
Pipeline #16882 canceled with stage
**前言**
在前端开发过程中,有时候会遇到一些不能使用分页来加载数据的情况,因此当我们需要渲染上十万条数据的时候,可能会造成渲染的卡顿,导致用户体验特别不好,那么对于这种情况我们该怎么去解决呢?这个时候就不得不提到虚拟列表
**什么是虚拟列表**
虚拟列表其实是按需显示的一种实现,即只对可见区域进行渲染,对非可见区域中的数据不渲染或部分渲染的技术,从而达到极高的渲染性能。假设有10万条记录需要同时渲染,我们屏幕的可见区域的高度为550px,而列表项的高度为55px,则此时我们在屏幕中最多只能看到10个列表项,那么在渲染的时候,我们只需加载可视区的那10条即可
**为啥需要使用虚拟列表**
虚拟列表可以解决一次性渲染数据量过大时,页面卡顿,(比如: table不分页并且一次性加载上万条复杂的数据)
**成熟的虚拟列表方案**
Ant-design — 虚拟列表
但是这些成熟的方案不一定能满足你的需求(比如:antd官网这个案列不支持hover时候行高亮),因此,我们需要知道这个虚拟列表的实现原理,以便在以后的工作中,随机应变,举一反三
**自己实现**
实现步骤
外层容器高度固定(550),并且设置(overflowY: ‘auto’)
计算内层容器放下全部数据的高度(total * 55)
计算在这个固定容器高度下可视区可以装多少条数据(limit = 550 / 55)
滚动的时候,通过参数scrollTop,可以计算可视区展示的第一条数据是第几条(startIndex = scrollTop / 55 )和最后一条数据是第几条(endIndex = limit + startIndex)
优化,加一个缓冲条数bufferSize,防止鼠标滚动过快的时候出现空白区域
固定外层容器高度
```
<div
style={{
margin: '0 auto',
height: '550px',
width: '100%',
overflowY: 'auto',
msOverflowX: 'hidden',
// 为了直观点这里用红色表示外层容器高度
border:'1px solid red',
}}
onScroll={onScroll}
ref={ref}
>
<div>{/* 内存容器 */}</div>
</div>
```
向容器中添加一层内层容器,并计算在总数据下,内层容器被撑开的最大高度
```
<div
style={{
margin: '0 auto',
height: '550px',
width: '100%',
overflowY: 'auto',
msOverflowX: 'hidden',
border:'1px solid red',
}}
onScroll={onScroll}
ref={ref}
>
<div style={{ height: `${total * 55}px`, position: 'relative' }}>
{/* 大量的数据 */}
</div>
</div>
```
计算在外层容器高度范围内可见的数据条数
```
limit = 550(外层容器的height) / 55(每一条数据的行高)
1
计算可视区第一条和最后一条数据分别在总数据的第几条
const limit = 10;
let originStartIdx = 0;
// total是总共有多少条数据
const total = 100000;
// rowHeight是每一条数据的行高
const rowHeight = 55;
const vTable = () => {
const [startIndex, setStart] = useState(Math.max(originStartIdx, 0));
const [endIndex, setEnd] = useState(Math.min(originStartIdx + limit, total - 1));
const onScroll = (e: any) => {
if (e.target === ref.current) {
const { scrollTop } = e.target;
// scrollTop 获取到的是被滚动元素在垂直位置滚动的距离。
const currIndex = Math.floor(scrollTop / rowHeight;
if (originStartIdx !== currIndex) {
originStartIdx = currIndex;
setStart(Math.max(currIndex, 0))
setEnd(Math.min(currIndex + limit, total - 1))
}
}
}
}
```
这样滑动过快会出现空白区域
添加缓冲参数,防止滚动过快出现空白
```
const limit = 10;
let originStartIdx = 0;
// total是总共有多少条数据
const total = 100000;
// rowHeight是每一条数据的行高
const rowHeight = 55;
// 用于缓冲(防止滚动过快,出现白屏)
const bufferSize = 20;
const vTable = () => {
const [startIndex, setStart] = useState(Math.max(originStartIdx - bufferSize, 0));
const [endIndex, setEnd] = useState(Math.min(originStartIdx + limit + bufferSize, total - 1));
const onScroll = (e: any) => {
if (e.target === ref.current) {
const { scrollTop } = e.target;
// scrollTop 获取到的是被滚动元素在垂直位置滚动的距离。
const currIndex = Math.floor(scrollTop / rowHeight;
if (originStartIdx !== currIndex) {
originStartIdx = currIndex;
setStart(Math.max(currIndex - bufferSize, 0))
setEnd(Math.min(currIndex + limit + bufferSize, total - 1))
}
}
}
}
```
添加后滑动过快空白消失
全部代码
```
```go
import { useState, useRef } from "react";
import { select } from "@querycap-ui/core";
const total = 100000;
const rowHeight = 55;
// 用于缓冲(防止滚动过快,出现白屏)
const bufferSize = 20;
const limit = 10;
let originStartIdx = 0;
const getdata = () => {
const data = [];
for (let i = 0; i < total; i++) {
data.push({
title: `标题${i}`,
age: `年纪${i}`,
address: `地址${i}`,
})
}
return data;
}
const data = getdata();
const Home = () => {
const [current, setCurrent] = useState<undefined | number>();
const [startIndex, setStart] = useState(Math.max(originStartIdx - bufferSize, 0));
const [endIndex, setEnd] = useState(Math.min(originStartIdx + limit + bufferSize, total - 1));
const ref = useRef<HTMLDivElement>(null);
const onScroll = (e: any) => {
if (e.target === ref.current) {
const { scrollTop } = e.target;
// scrollTop 获取到的是被滚动元素在垂直位置滚动的距离。
const currIndex = Math.floor(scrollTop / rowHeight);
if (originStartIdx !== currIndex) {
originStartIdx = currIndex;
setStart(Math.max(currIndex - bufferSize, 0))
setEnd(Math.min(currIndex + limit + bufferSize, total - 1))
}
}
}
const edite = (data: any, index: number) => {
setCurrent(index);
console.log('编辑了这一条数据---->',data)
}
const rowRenderer = (obj: any) => {
const { index, style } = obj;
return (
<div
key={index}
style={style}
css={
select().backgroundColor(index === current ? '#dcebf8' : '').display('flex')
.with(select(':hover').backgroundColor(index === current ? '#dcebf8' : '#fafafa'))
.with(select('>*').flex(1).padding('0px 16px'))
}
>
<span>{data[index].title}</span>
<span>{data[index].age}</span>
<span>{data[index].address}</span>
<a onClick={() => edite(data[index], index)}>编辑</a>
</div>
)
}
// 可视区域的数据
const pushData = () => {
const content = [];
for (let i = startIndex; i <= endIndex; ++i) {
content.push(
rowRenderer({
index: i,
style: {
height: `${rowHeight}px`,
lineHeight: `${rowHeight}px`,
left: 0,
right: 0,
position: "absolute",
top: i * rowHeight,
borderBottom: "1px solid #f0f0f0",
width: "100%",
cursor: 'pointer',
}
})
);
}
return content;
}
return (
<div
style={{
margin: '0 auto',
height: '550px',
width: '100%',
overflowY: 'auto',
msOverflowX: 'hidden',
border:'1px solid red',
}}
onScroll={onScroll}
ref={ref}
>
<div style={{ height: `${total * 55}px`, position: 'relative' }}>
{pushData()}
</div>
</div>
);
};
```
```
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment