虚拟列表实现原理

虚拟列表通过只渲染可视区域的元素,显著提升了页面性能。本文介绍了虚拟列表的核心原理和实现步骤,包括监听滚动事件、计算item高度、确定显示范围、数据截取以及绝对定位等关键点。

1、虚拟列表 核心原理就是 比如有100条数据 如果全部渲染出来,那么dom结构里面对应有100个对应的dom,比如1000 10000的时候,就会开始影响页面性能,那么就要实现虚拟列表, dom结构里面永远只渲染10条 或者 页面可展示的条数,那么性能就会大大提升!

2、实现原理

function Home(props: Props) {
    let lessonListRef = useRef(null);
    useEffect(()=>{
        lessonListRef.current();
        //重新渲染组件
        homeContainerRef.current.addEventListener('scroll',throttle(lessonListRef.current,16));
    },[]);//依赖数组为空表示此effect回调只会执行一次
    return (
        <>
            <div className="home-container" ref={homeContainerRef}>
                <LessonList 
                  lessons={props.lessons} 
                  getLessons={props.getLessons}
                  ref={lessonListRef}
                  homeContainerRef={homeContainerRef}
                />
            </div>
        </>
    )
}
  • 2.1 先定义ref : lessonListRef传入给LessonList组件,外层容器homeContainerRef绑定scroll事件,那么容器滚动的时候,就会执行lessonListRef.current方法。

  • 2.2 let [,forceUpdate] = React.useReducer(x=>x+1,0); 只是为了定义一个强制刷新的函数forceUpdate 然后赋值给forwardedRef.current = forceUpdate;

  • 2.3 remSize 就是1rem代表多少px 比如37.5

  • 2.4 itemSize 就是计算当前每个item高度是多少px 比如原来是650px的设计图高度 650 /75就是rem rem*remSize就是实际高度

  • 2.5 screenHeight 显示内容的高度

  • 2.6 scrollTop 父容器滚动的距离

  • 2.7 start = Math.floor(scrollTop/itemSize); start索引就是展示的起始索引 因为卷去的高度/item高度 就是代表滚到第几个

  • 2.8 end = start + Math.floor(screenHeight/itemSize); screenHeight/itemSize就是屏幕最多展示的个数 开始+展示个数 就是end

  • 2.9 为了滚动时 前后都看到有数据 start做减法-2 end做加法 + 2, 前后分别都加2个数据看到

  • 3.0 visibleList 就是展示的list, 通过start end动态截取原始数据展示出来即可

  • 3.1 关键是每个item都是绝对定位通过 top改变他的位置 top就是index*itemSize

  • 3.2因为一开始就调用监听scroll 调用forceUpdate 所以每次滚动都会修改start end值 改变数据, 并且改变每个item top的值

function LessonList(props: PropsWithChildren<Props>,forwardedRef:any) {
    //只是为了让我们得到一个强行刷新函数组件的方法
    let [,forceUpdate] = React.useReducer(x=>x+1,0);
    useEffect(() => {
        if (props.lessons.list.length === 0) {
            props.getLessons();
        }
        forwardedRef.current = forceUpdate;
    },[]);
    let remSize = parseFloat(document.documentElement.style.fontSize);//750px=>75px 375px=>37.5px
    const itemSize = (650/75)*remSize;//每个条目实际的高度
    //window.innerHeight=屏幕的高度=100vh -实际际的头和尾的高度  750px header 100px tab=121px=221
    const screenHeight = window.innerHeight - (221/75) * remSize;//显示内容的容器的高度
    const homeContainer = props.homeContainerRef.current;//DOM元素
    let start=0,end=0;
    if(homeContainer){
        const scrollTop = homeContainer.scrollTop;//父容器向上卷去的高度
        start = Math.floor(scrollTop/itemSize);
        end = start + Math.floor(screenHeight/itemSize);
        start -=2,end+=2;//右闭右开的区间
        start=start<0?0:start;//小于0取0
        end=end>props.lessons.list.length?props.lessons.list.length:end;// 如果大于最后一个,取最大值
    }
    //可视条目的列表
    const visibleList:VisibleLesson[]  = props.lessons.list.map((item:Lesson,index:number)=>({
        ...item,index
    })).slice(start,end);
    const basicStyle:React.CSSProperties  = {
        position:'absolute',
        top:0,
        left:0,
        width:'100%',
        height:itemSize
    }
    return (
        <section className="lesson-list">
            <h2><MenuOutlined />全部课程</h2>
            <div style={{position:'relative',width:'100%',height:`${props.lessons.list.length*itemSize}px`}}>
            {
                    visibleList.map((lesson: VisibleLesson, index: number) => (
                        <Link 
                        key={lesson.id} 
                        to={{ pathname: `/detail/${lesson.id}`, state: lesson }}
                        style={{...basicStyle,top:`${lesson.index*itemSize}px`}}
                        >
                            <Card
                                hoverable={true}
                                style={{ width: '100%' }}
                                cover={<img alt={lesson.title} src={lesson.poster} />}
                            >
                                <Card.Meta
                                    title={lesson.title}
                                    description={`价格:${lesson.price}`}
                                />
                            </Card>
                        </Link>
                    ))
                }
            </div>
            {
                props.lessons.hasMore ? (
                    <Button onClick={props.getLessons} loading={props.lessons.loading} type="primary" block
                    >{props.lessons.loading ? "" : "加载更多"}</Button>) : <Alert
                    style={{ textAlign: 'center' }}
                    message="到底了"
                    type="warning"
                />

            }
        </section>
    )
}

export default React.forwardRef(LessonList);

总结:滚动事件监听,计算每个item高度, 计算滚动的距离, 计算start end ,做数据截取,展示截取后的数据, 同时做样式绝对定位就可以 (top值就可以改变处于的高度)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值