React如何正确实现列表无限加载?解决isFetching、分页传值问题
无限加载功能可落地方案
核心实现逻辑
按照现有组件结构,仅需调整3个核心环节即可跑通全流程,无需重构现有组件树:
1. 补全isFetching状态的控制闭环
这个状态的核心作用是拦截重复触发的滚动请求,避免短时间内重复发起多页数据加载。
对应的子组件调整逻辑:
- 滚动触发前先判断:如果当前
isFetching为true、或者传入的hasMore标记为false,直接拦截不执行后续逻辑 - 新增状态重置逻辑:当传入的
datasetList长度发生变化时,说明本次拉取的数据已经渲染完成,直接把isFetching重置为false - 原空置的
useEffect调整为加载触发的校验逻辑:当isFetching变为true时,先校验是否有更多数据,确认无误后再调用父组件的加载方法
调整后的子组件核心代码:
// 监听列表数据变化,重置加载状态 useEffect(() => { if (isFetching) { setIsFetching(false); } }, [datasetList, isFetching]); // 原空置的useEffect调整为加载触发逻辑 useEffect(() => { if (isFetching) { // 没有更多数据时,直接重置状态不发请求 if (!hasMore) { setIsFetching(false); return; } fetchMoreDatasetItems(); } }, [isFetching, hasMore, fetchMoreDatasetItems]); // 调整滚动触发逻辑,增加前置拦截 function handleScroll() { // 已经在请求中、没有更多数据时直接中断 if (isFetching || !hasMore) return; if ( myscroll.current.scrollTop + myscroll.current.clientHeight >= myscroll.current.scrollHeight ) { setIsFetching(true) } }
2. 父组件分页参数的累加逻辑
原来固定写死datasetPage: 0是每次都拉第一页的核心原因,需要在父组件里单独维护分页的全局状态:
- 把固定的分页大小抽为常量,避免硬编码:
const DATASET_PAGE_SIZE = 3; - 在父组件的state里新增两个状态:
currentDatasetPage: 记录当前已经加载完成的页码,初始值为0hasMoreDataset: 标记列表是否还有下一页可以加载,初始值为true
调整后的fetchMoreDatasetItems方法:
// 抽离固定分页常量 const DATASET_PAGE_SIZE = 3; fetchMoreDatasetItems = () => { const { dispatch, routeProps: { match: { params: { Aid } } } } = this.props; // 没有Aid、没有更多数据、正在加载时直接中断 if (!Aid || !this.state.hasMoreDataset || this.state.isDatasetLoading) return; // 累加页码,拉取下一页 const nextPage = this.state.currentDatasetPage + 1; this.setState({ isDatasetLoading: true }); dispatch(loadDatasetList({ Aid, datasetPage: nextPage, datasetPageSize: DATASET_PAGE_SIZE, // 给dispatch传回调,拿到接口返回的数据后做后续处理 onSuccess: (newPageData) => { // 计算新页长度:如果返回长度小于分页大小,说明已经是最后一页 const currentHasMore = newPageData.length >= DATASET_PAGE_SIZE; this.setState({ currentDatasetPage: nextPage, hasMoreDataset: currentHasMore, isDatasetLoading: false }) } })) }
注:如果使用Redux类状态管理,需要在对应reducer中调整逻辑:分页接口返回的新数据和原有列表做拼接,不做全量替换。
3. 新数据的拼接与父子组件传参调整
数据拼接逻辑
在状态管理(如redux的reducer)处理分页数据时,不要直接用新分页数据覆盖原来的datasetList,需要做拼接:
// reducer里处理loadDatasetList返回的逻辑 case LOAD_DATASET_LIST_SUCCESS: return { ...state, // 原有列表 + 新分页返回的数据 datasetList: [...state.datasetList, ...action.payload.newPageItems] }
传参调整
原来给子组件传的props新增1个参数,让子组件能正确判断加载状态:
<TableActionList datasetList={datasetList} fetchMoreDatasetItems={this.fetchMoreDatasetItems} // 新增:是否还有更多数据 hasMore={this.state.hasMoreDataset} />
子组件的props接收逻辑同步调整:
const DatasetPopOut = ({ datasetList, changeDataset, selectedDataSet, fetchMoreDatasetItems, hasMore // 接收父组件传的是否还有更多的标记 }) => { // ... 原有逻辑 }
可扩展优化点
- 子组件列表底部加简单加载提示:当
isFetching为true时,渲染“加载中...”文本,提升用户体验 - 滚动监听逻辑增加100ms左右的防抖,避免滚动过程中频繁触发位置计算
- 当
hasMore为false时,在列表底部渲染“已经到底啦”提示,避免用户无意义滚动
内容的提问来源于stack exchange,提问作者bros
相关产品推荐
相关产品推荐

