虚拟列表的渐进式实现(vue,react)
1、地址(vue)
2、react (下面是几个版本 样式在最后面)
/** @Author: your name
* @Date: 2020-03-13 11:11:23
* @LastEditTime: 2020-05-13 16:39:01
* @LastEditors: Please set LastEditors
* @Description: 列表项等宽
* @FilePath: /optimus/src/pages/test/Index/hooks.js
*/
import React, { useState, useEffect, useRef } from "react";
import styles from "./style.less";
const arr = [];
for (let index = 0; index < 200; index++) {
arr.push({
index,
height: parseInt(Math.random() * 30 + 20)
});
}
const Index = (props) => {
const [height, setHeight] = useState(0);
const [itemHeight] = useState(30);
const [totalList] = useState(arr);
const [list, setList] = useState([]);
const couterRef = useRef();
const totalRef = useRef();
useEffect(() => {
setHeight(totalList.length * itemHeight);
}, [totalList]);
useEffect(() => {
updateVisibleData();
}, [totalList]);
const onScrollCapture = (view) => {
const scrollTop = totalRef.current.scrollTop;
updateVisibleData(scrollTop);
};
const updateVisibleData = (scrollTop) => {
scrollTop = scrollTop || 0;
const clientHeight = totalRef.current.clientHeight;
const visibleCount = Math.ceil(clientHeight / itemHeight);
const start = Math.floor(scrollTop/ itemHeight);
const end = start + visibleCount;
const _list = totalList.slice(start, end);
setList(_list);
couterRef.current.style.webkitTransform = `translate3d(0, ${start * itemHeight}px, 0)`;
};
return (
<div className={styles.wrap}>
<div
className={styles.listView}
ref={totalRef}
onScrollCapture={onScrollCapture}
>
<div
className={styles.listViewPhantom}
style={{ height: height }}
></div>
<div className={styles.listViewContent} ref={couterRef}>
{list.map((item, index) => {
return (
<div
className={styles.listViewItem}
style={{ height: itemHeight }}
key={index}
>
{item.index}
</div>
);
})}
</div>
</div>
</div>
);
};
export default Index;
/** @Author: x
* @Date: 2020-03-13 11:11:23
* @LastEditTime: 2020-05-13 16:34:47
* @LastEditors: Please set LastEditors
* @Description: react 列表项不等宽
* @FilePath: /optimus/src/pages/test/Index/hooks.js
*/
import React, { useState, useEffect, useRef } from "react";
import styles from "./style.less";
const arr = [];
for (let index = 0; index < 200; index++) {
arr.push({
index,
height: parseInt(Math.random() * 30 + 20)
});
}
const Index = (props) => {
const [height, setHeight] = useState(0);
const [totalList, setTotalList] = useState(arr);
const [list, setList] = useState([]);
const couterRef = useRef();
const totalRef = useRef();
useEffect(() => {
let total = 0;
let index = 0;
let length = totalList.length;
for (index; index < length; index++) {
total += totalList[index].height;
}
setHeight(total);
}, [totalList]);
useEffect(() => {
updateVisibleData();
}, [totalList]);
const onScrollCapture = (view) => {
const scrollTop = totalRef.current.scrollTop;
updateVisibleData(scrollTop);
};
const updateVisibleData = (scrollTop) => {
scrollTop = scrollTop || 0;
const start = findNearestItemIndex(scrollTop);
const end = findNearestItemIndex(scrollTop + totalRef.current.clientHeight);
const _list = totalList.slice(start, Math.min(end + 1, totalList.length));
setList(_list);
couterRef.current.style.webkitTransform = `translate3d(0, ${getItemSizeAndOffset(start).offset}px, 0)`;
};
const findNearestItemIndex = scrollTop => {
let total = 0;
for (let i = 0, j = totalList.length; i < j; i++) {
const size = totalList[i].height;
total += size;
if (total >= scrollTop || i === j -1) {
return i;
}
}
return 0;
}
const getItemSizeAndOffset = start => {
let total = 0;
for (let i = 0, j = Math.min(start, totalList.length - 1); i <= j; i++) {
const size = totalList[i].height;
if (i === j) {
return {
offset: total,
size
};
}
total += size;
}
return {
offset: 0,
size: 0
};
}
return (
<div className={styles.wrap}>
<div
className={styles.listView}
ref={totalRef}
onScrollCapture={onScrollCapture}
>
<div
className={styles.listViewPhantom}
style={{ height: height }}
></div>
<div className={styles.listViewContent} ref={couterRef}>
{list.map((item, index) => {
return (
<div
className={styles.listViewItem}
style={{ height: item.height }}
key={index}
>
{item.index}
</div>
);
})}
</div>
</div>
</div>
);
};
export default Index;
.wrap{width: 100%;
height: 800px;
display: flex;
justify-content: center;
align-items: center;
background-color: antiquewhite;
}
.listView {
height: 400px;
width: 160px;
overflow: auto;
position: relative;
border: 1px solid #aaa;
}
.listViewPhantom {
position: absolute;
left: 0;
top: 0;
right: 0;
z-index: 100;
width: 160px;
background-color: rgba(red,0.5);
}
.listViewContent {
left: 0;
right: 0;
top: 0;
position: absolute;
}
.listViewItem {
padding: 5px;
color: #666;
box-sizing: border-box;
border-bottom: 1px solid red;
display: flex;
align-items: center;
}
写在最后
题目当然还可以再优化:
对itemHeight的缓存;
对contextHeight的高度计算;
对缓存结果的算法查询;
对未缓存结果的算法查询;
根据渲染结果动态更新列表项的高度;
数据源更新时尽量范围小的删除失效缓存;
。。。
优化之路永无尽头;
以上是 虚拟列表的渐进式实现(vue,react) 的全部内容, 来源链接: utcz.com/z/380678.html