React 长列表加载 实现虚拟列表

React 长列表加载 实现虚拟列表

效果


实现思路

 定义一个container 高为一屏高
 定义一个listWrapper 高为所有列表元素的高度,来撑开容器
 定义一个itemWrapper 高为一屏高度,来跟随上、下拉操作进行位移,从而总是覆盖展示在当前屏
 
 滚动时关键值计算:
    一屏个数 limit: 1+(Math.ceil(containerHeight / itemHeight))  加1是预加载,多加载一项减少抖动
    start: 一屏的起始索引 Math.floor(scrollTop / itemHeight) 即滚动了几个元素
    end: 一屏的结束索引 start+limit
    transformY: 
                下拉的位移。 滚动时,根据scrollTop计算出滚动过多少个元素,设置对应的start和end, 那么listWrapper的transformY值即为start*itemHeight(一屏起始索引*列表项高度)。下拉多少,就向下位移多少,让其一直展示在首屏

具体实现


ReactVirtualList

import React, { useState, useMemo, memo } from 'react'
import './ReactVirtualList.css'
import { ListItem } from './ListItem';
import { useCallback } from 'react';
import { useRef } from 'react';

const ReactVirtualList = (props) => {
    let { list, item: Item, contentWidth, contentHeight, itemHeight } = props

    const [start, setStart] = useState(0)

    const listDom = useRef()

    const limit = useMemo(() => {
        return 1 + Math.ceil(contentHeight / (itemHeight))
    }, [contentHeight, itemHeight]);

    const scrollHandler = useCallback(
        (e) => {
            const top = e.target.scrollTop
            const curStart = Math.floor(top / (itemHeight))
            curStart !== start && setStart(curStart)
        },
        [itemHeight, start]
    )

    const end = useMemo(() => {
        return Math.min(start + limit, list.length)
    }, [start, limit, list]);

    const renderList = useMemo(() => {
        return list
            .slice(start, end)
            .map((v, i) => (
                <ListItem key={v.id} id={v.id} itemHeight={itemHeight} >
                    <Item text={v.v}></Item>
                </ListItem>
            ))
    }, [start, end, list, itemHeight]);

    const transformY = useMemo(() => {
        return start * itemHeight + 'px'
    }, [start, itemHeight]);

    return <ul className='island-virtual-list' ref={listDom} onScroll={(e) => scrollHandler(e)} style={{ width: contentWidth + 'px', height: contentHeight + 'px' }}>
        <div className="listWrapper" style={{ height: itemHeight * list.length + 'px' }}>
            <div className="itemWrapper" style={{ height: contentHeight + 'px', transform: `translate3d(0, ${transformY}, 0)` }}>
                {renderList}
            </div>
        </div>
    </ul>

}

export default memo(ReactVirtualList)

样式

.island-virtual-list {
    margin: auto;
    position: absolute;
    top: 0;
    left: 0;
    bottom: 0;
    right: 0;
    opacity: 0.8;
    overflow-y: auto;
    overflow-x: hidden;
    border: 1px solid #ccc;
}

.listWrapper {
    display: flex;
    flex-direction: column;
    width: 100%;
}

.island-virtual-list-item {
    box-sizing: border-box;
    width: 100%;
    font-size: 12px;
    display: flex;
    align-items: center;
    justify-content: center;
    border-bottom: 1px solid black;
}


.itemWrapper {
    transform: translate3d(0, 0, 0);
}

使用

import './App.css';
import ReactVirtualList from './component/ReactVirtualList';

const styleObj = {
  contentWidth: 800,
  contentHeight: 300,
  itemHeight: 50,
  itemWidth: 60
}

const Item = (props) => {
  return <div className='list-item'>{props.text}</div>
}

const list = Array(Math.floor((Math.random() + 1) * 10000)).fill().map((v, i, arr) => ({ id: i, v: i + '/' + arr.length + '   行' }))

function App() {
  return <div className='app'>
    <ReactVirtualList {...styleObj} list={list} item={Item} ></ReactVirtualList>
  </div>
}

export default App;
posted @ 2022-04-01 14:08  IslandZzzz  阅读(523)  评论(0编辑  收藏  举报