乐闻世界logo
搜索文章和话题
如何封装React无限滚动加载列表组件【含源码】

如何封装React无限滚动加载列表组件【含源码】

乐闻的头像
乐闻

2023年05月19日 10:44· 阅读 2802

前言

由于需要考虑后端接口的性能问题,我们在请求业务数据列表的时候并不能直接请求全量数据。所以我们在请求数据时常见的方式是做分页查询。

对于前端交互而言,我们需要考虑如何优雅的让用户触发请求下一页数据的接口。常用的方法有两种:1. 提供显示的分页器,让用户自己手动点击下一页;2. 业务滚动到某个阈值时自动触发下一页请求。

对于移动端,滚动加载的交互是更加优雅的处理方式。对于滚动加载的能力,我们需要一个公共的组件来实现代码的复用,避免每次都要为滚动加载的需求伤脑筋。

效果图

先看效果,增加信心

Untitled

准备工作

滚动事件的参数中核心属性

clientWidth可视区宽度
clientHeight可视区高度
offsetWidth可视区宽度
offsetHeight可视区高度
scrollWidth内容实际宽度
scrollHeight内容实际高度
scrollTop内容顶部距离可视区顶部距离
scrollLeft内容左侧距离可视图左侧距离

比较直观的示意图

实现原理

滚动加载的目的是用户滚动页面到最底部时可以自动请求下一页的数据接口,所以问题重点是如何确认用户的页面滚动到了最底部。

列表触底的条件:可视区高度 + 滚动距离 ≥ 内容实际高度

offsetHeight + scrollTop ≥ scrollHeight

核心代码

jsx
scrollEvent = async (e) => { let scrollHeight = e.target.scrollHeight; let scrollTop = e.target.scrollTop; let offsetHeight = e.target.offsetHeight; if (offsetHeight + scrollTop >= scrollHeight) { console.log('列表触底,触发接口请求数据'); this.setState({ loading: true }); let result = await this.loadData(); this.setState({ loading: false, list: this.state.list.concat(result), }); } }; window.addListener('scroll',()=>scrollEvent())

组件封装

为了让代码能够更高的得到复用,将代码封装成UI组件是必要的,于是我封装了一个简易的React版的无限滚动组件。

组件能力介绍

  • 业务方管理数据源dataSource,便于个性化业务操作;
  • 支持自定义数据加载中skeleton;
  • 增加触发门槛,减少无效的数据请求;
js
import React, { ReactNode, useEffect, useRef } from 'react' import classnames from 'classnames' interface InfiniteScrollListProps<T> { loading: boolean dataSource: Array<T> renderItem: (data: T) => ReactNode renderSkeleton?: () => ReactNode hasMore: boolean loadMore: () => void className?: string } export default function InfiniteScrollList<T>(props: InfiniteScrollListProps<T>) { const { loading, dataSource, renderItem, renderSkeleton, loadMore, hasMore, className } = props const containerRef = useRef<HTMLDivElement>(null) useEffect(() => { const scrollEvent = (event) => { if (!hasMore || loading) return //可视区高度 let scrollHeight = event.target?.scrollHeight //滚动高度 let scrollTop = event.target.scrollTop //列表内容实际高度 let offsetHeight = event.target.offsetHeight if (offsetHeight + scrollTop >= scrollHeight) { console.log('列表触底') loadMore() } } containerRef.current?.addEventListener('scroll', scrollEvent) return () => { containerRef.current?.removeEventListener('scroll', scrollEvent) } }, [hasMore, loading]) return ( <div className={classnames('flex-1 flex flex-col overflow-y-auto', className)} ref={containerRef}> {dataSource.map((data) => { return renderItem(data) })} {loading && new Array(4).fill(0).map(() => renderSkeleton?.())} </div> ) }
shell
标签: