虚拟列表的几种实现方式
1 .ahooks里面的关键代码
height:totalHeight-offsetTop,
marginTop:offsetTop,
1 .只有一层dom结构
2 .一般实现
1 .一个div来展示总的高度,用来显示滚动条,并接收滚动事件
2 .下面有一个div,根据滚动条接收到的滚动距离来移动div。这样需要多写一些dom结构
3 .react-virtualized 支持千万级的数据展示
4 .react-window更快,但是展示的数据没有上面的多
1 .看css样式变化,好像是分别给每个子div添加绝对定位,通过这个来给元素确定位置,感觉这样计算量有点大。
2 .https://bvaughn.github.io/react-virtualized/#/components/List
3 .可以看例子研究下
4 .react-virtualized 9千万数据的时候就不行了,还是可以滚动,就是会出现延迟显示,偶然的页面卡死
问题,快速拖动滚动条的时候,会出现白屏界面
1 .
列表高度不变的代码
1 .559240,为什么只能显示这么多,后面的就显示不了了。而且页面发现有卡顿。为什么比这个小的数据都是正常显示的
2 .为什么感觉最大显示数量是和每个item的高度有关的,item越小,显示的越多,做多是160多万条数据
3 .这个好像真的是这种写法的局限性。100万以上的数据就会显示有问题,总之就是height和margin-top这种显示的问题.ahook本来的组件也是不行,显示不全和卡顿
4 .最少的显示其实不确定,因为他的正常数据是和每一个子项的高度有关的
性能优化
1 .超大数组的时候,访问这个数组还是会有性能问题
2 .百万数据的时候我猜执行性能会在从百万数据截取数据的时候,所以泡个node代码测试下不同数量大小数组使用slice截取数据花费的时间,测试发现和数组大小没关系,花费时间是和截取的多少有明显的关系
1 .那是什么在影响他的性能呢!
202091918137.png
202091918241.png
最简单的写法
import {useEffect,useState,useMemo,useRef,MutableRefObject,useCallback} from 'react'
import useSize from '../src/useSize'
export interface OptionType {
itemHeight:number
overscan?:number,
}
export default function useVirtuallList<T = any>(list:T[],options:OptionType){
const containerRef = useRef<HTMLElement | null>()
const size=useSize(containerRef as MutableRefObject<HTMLElement>)
const [state,setState]=useState({start:0,end:10})
const {itemHeight,overscan=0}=options
let len=list.length
const getViewCapacity=useMemo(()=>{
if(containerRef.current){
return Math.ceil(containerRef.current.clientHeight/itemHeight)
}return 10
},[itemHeight,containerRef.current])
const getOffset=(scrollTop:number)=>{
return Math.floor(scrollTop/itemHeight)+1
}
const calculateRange=()=>{
const el=containerRef.current
if(el){
const offset=getOffset(el.scrollTop)
const from=offset-overscan
const to=offset+getViewCapacity+overscan
setState({
start:from<0?0:from,
end:to>len?len:to,
})
}
}
const totalHeight=useMemo(()=>{
return len*itemHeight
},[len,itemHeight])
// 获取全部元素的高度
const getDistanceTop=useCallback((index:number)=>{
return index*itemHeight
},[itemHeight])
// 根据当前的index元素,算出应该滚动的高度
// const scrollTo=(index:number)=>{
// if(index===0)return
// if(containerRef.current){
// containerRef.current.scrollTop=getDistanceTop(index)
// calculateRange()
// }
// }
// 滚动到index位置
// const scrollBy=(distance:number)=>{
// if(containerRef.current){
// containerRef.current.scrollBy(0,distance)
// calculateRange()
// // 这里有异步操作,会导致滑动的时候没有数据操作,当不在最下面的时候,是正常移动的,但是每当有新增数据的时候,往往慢半拍
// // 所以还是加一个参数来实现是否每次新增数据都移动的操作吧
// }
// }
//以一定距离进行滚动
const offsetTop=useMemo(()=>{
return state.start*itemHeight
},[itemHeight, state.start])
const showList=useMemo(()=>{
return list.slice(state.start,state.end)
},[state.start,state.end])
useEffect(()=>{
calculateRange()
},[size.width,size.height])
return {
list: showList,
containerProps: {
ref: function(ele: any){
containerRef.current = ele;
},
style: { overflowY: 'auto' as const },
onScroll:(e:any)=>{
e.preventDefault()
calculateRange()
}
},
wrapperProps:{
style:{
width:'100%',
height:totalHeight-offsetTop,
marginTop:offsetTop,
}
},
}
}
另一种写法
1 .这种最大也是80多万。到底是那里的极限被碰到了
2 .都是下面还有元素,但是父组件的滚动条已经到底了,不能继续向下滚动了
3 .33554400 渲染一个div最大的值就是这么大,height最大值 16777100他们最大的高度是这么大,我就知道不能一直大的
4 .https://stackoverflow.com/questions/16637530/whats-the-maximum-pixel-value-of-css-width-and-height-properties
问题
1 .transform3d滑动的时候都会出现抖动,想要react-spring来包装一下滑动,感觉需要加一个动画函数.但是用不出来...
2 .scroll-behavior: smooth .最后加个这个属性先勉强一下吧
3 .限制频率:最小一次滚动鼠标,会移动100px的距离,但是看日志确实触发了很多次scroll函数,其实中间的根本不必要触发,因为这里的计算结果我们根本不需要,所以这里可以做下频率限制.还有一个问题就是,既然我们是以100px的最小距离往下滑动,在列表里面,如果列表的高度是小于100px的,那么可能会在滑动的时候直接跳过这一项不显示。加了限制频率的函数发现滚动又不流畅了。。
4 .所以还是滚动的那里不加,计算现实的那个加吧。但是这里的函数是需要传值的,限流函数好像都一直不能传参数的28
5 .// willChange:'transform':加了这个属性,滚动的时候会出现不流畅的情况。就是惯性停留在原地。感觉这个加错地方了,应该加载移动的子元素上面,而不是加载滚动条div上willChange:'scroll-position',
6 .https://developer.mozilla.org/zh-CN/docs/Web/CSS/will-change
1 .不能将will-change应用在太多的元素上,过度使用会导致页面响应速度缓慢或者消耗非常多的资源
2 .当元素变化前和之后通过脚本来切换是否应用这个属性,不要让他一直挂在元素上面
3 .不要太早的使用这个属性,这个是最后的保障,初期为了一点速度就使用这个是不合适的,应该先去尝试解决真正的性能问题
4 .
最终版本,使用百分比决定相对显示的数据
import {useEffect,useState,useMemo,useRef,MutableRefObject,useCallback} from 'react'
import useSize from '../src/useSize'
import {useThrottleFn} from 'ahooks'
export interface OptionType {
itemHeight:number
overscan?:number,
}
let maxHeight=16777100
// 别的库最大高度就是这个.
export default function useVirtuallList<T = any>(list:T[],options:OptionType){
const containerRef = useRef<HTMLElement | null>()
const size=useSize(containerRef as MutableRefObject<HTMLElement>)
const [state,setState]=useState({start:0,end:10})
const [scrollTop,setScrollTop]=useState(0)
// 每个补偿的高度,默认是0
const {itemHeight,overscan=0}=options
let len=list.length
const getLen=useMemo(()=>{
return len.toString().length
},[len])
const getNum=useMemo(()=>{
let num=1
for(let i=0;i<getLen;i++){
num*=10
}
return num
},[getLen])
const getViewCapacity=useMemo(()=>{
if(containerRef.current){
return Math.ceil(containerRef.current.clientHeight/itemHeight)
}return 10
},[itemHeight,containerRef.current])
const {run}=useThrottleFn(()=>{
let from=(Number((scrollTop/totalHeight).toFixed(getLen))*getNum)-overscan
// 还需要判断下是不是整数,看log发现还是有异常情况7.000000000000001.100以内的情况
if(!Number.isInteger(from)){
from=Math.floor(from)
}
let to=from+getViewCapacity+overscan
setState({
start:from<0?0:from,
end:to>len?len:to,
})
},{
wait:100
// 感觉这么大还是跟手。一次鼠标滑动,这里触发3次
})
const calculateRange=()=>{
const el=containerRef.current
if(el){
if(el.scrollTop>totalHeight){
console.log('你不对劲')
return
}else{
setScrollTop(el.scrollTop)
run()
}
}
}
const totalHeight=useMemo(()=>{
let totalHeight=len*itemHeight
if(totalHeight>maxHeight){
return maxHeight
}return len*itemHeight
},[len,itemHeight])
const showList=useMemo(()=>{
return list.slice(state.start,state.end)
},[state.start,state.end,list])
useEffect(()=>{
calculateRange()
},[size.width,size.height])
return {
list: showList,
containerProps: {
ref: function(ele: any){
containerRef.current = ele;
},
style: {
overflowY: 'auto' as const,
position:'relative'
},
onScroll:(e:any)=>{
e.preventDefault()
calculateRange()
}
},
wrapperProps:{
style:{
width:'100%',
position:'absolute' as 'absolute',
top:0,
transform:`translate3d(0,${scrollTop}px,0)`,
willChange:'transform',
}
},
scrollBarProps:{
style:{
height:totalHeight,
willChange:'scroll-position',
}
}
}
}
function VirtualListItem(props:any){
const itemRef=useRef<HTMLDivElement|null>(null)
useEffect(()=>{
// if(!props.arrHeight[props.index]){
// console.log('添加了')
// // 这里其实还需要加上margin的高度,但是算的话有点没必要,反正这个值也是固定还
// if(itemRef.current){
// props.updateAt(props.index,itemRef.current.clientHeight+10)
// // 这里只有一个margin-bottom:所以只加10,要是别的就在这里另外算
// // 每次都要特殊设置
// }
// }
// 这里不用做判断了,就让他每次都更新吧。
// if(itemRef.current){
// props.updateAt(props.index,itemRef.current.clientHeight+10)
// // 这里只有一个margin-bottom:所以只加10,要是别的就在这里另外算
// // 每次都要特殊设置
// }
// console.log('首次创建',console.log(itemRef.current?.clientHeight))
},[])
return (
<div ref={itemRef}
style={{
height: 20,
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
// marginBottom: 8,
// marginTop:8,
wordBreak: 'break-word',
}}
>
{props.children}
</div>
)
}
const {list,containerProps,wrapperProps,scrollBarProps}=useList(arrList,{
overscan:1,
itemHeight:40,
})
<div
{...containerProps}
style={{
height:'200px',
width:'500px',
// willChange:'scroll-position',
overflow:"auto",
position:'relative',
scrollBehavior: "smooth",
overflowAnchor:"none",
}}>
<div {...scrollBarProps}></div>
<div {...wrapperProps}>
{list.map((el)=>(
<div key={el}
style={{
height:40,
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
}}
>
{el}
</div>
))}
</div>
</div>
网友评论