ListView的onEndReached异常:重复加载数据且自动触发
解决ListView重复数据+onEndReached无需滚动触发的问题
兄弟,我之前踩过完全一样的坑!你遇到的重复数据问题,核心就是两个原因:
- onEndReached无需滚动就触发:ListView初始渲染时,内容高度(contentSize)可能小于容器高度,组件直接判定已经到了底部,自动触发加载逻辑
- API重复调用page=1:没有对请求做防抖锁,加上页码更新不及时,导致多次请求同一个页码的数据,最终重复拼接
具体修复方案
我给你整理了经过验证的代码和关键优化点:
1. 新增状态锁与分页控制
首先给组件添加isLoading(加载锁)和hasMore(是否还有更多数据)状态,彻底避免重复请求:
state = { categories: [], page: 1, isLoading: false, // 加载中锁,防止并发请求 hasMore: true // 标记是否还有更多数据 };
2. 重构请求逻辑,用函数式setState避免闭包问题
因为setState是异步操作,直接用this.state可能拿到旧值,必须用函数式更新确保状态最新:
componentDidMount() { this.fetchCategories(); } fetchCategories = async () => { // 加载中或无更多数据时直接返回,阻断重复请求 if (this.state.isLoading || !this.state.hasMore) return; this.setState({ isLoading: true }); try { const response = await categoryApi.getOne(this.state.page); if (response.data && response.data.data.length > 0) { // 函数式setState获取最新状态,避免闭包陷阱 this.setState(prevState => ({ categories: [...prevState.categories, ...response.data.data], page: prevState.page + 1, // 成功请求后才更新页码 isLoading: false })); } else { // 没有更多数据了,关闭加载开关 this.setState({ isLoading: false, hasMore: false }); } } catch (err) { console.error('分类数据请求失败:', err); this.setState({ isLoading: false }); } };
3. 配置ListView的关键属性
调整onEndReachedThreshold,避免初始误触:
render() { return ( <ListView dataSource={new ListView.DataSource({ rowHasChanged: (r1, r2) => r1.id !== r2.id }).cloneWithRows(this.state.categories)} renderRow={(category) => <CategoryItem item={category} />} onEndReached={this.fetchCategories} onEndReachedThreshold={0.2} // 滚动到距离底部20%时触发,避免初始误触 renderFooter={() => this.state.isLoading && <ActivityIndicator size="small" color="#000" />} /> ); }
几个关键注意点
- 加载锁必须生效:每次请求前先判断
isLoading,防止用户快速滚动或者组件误触导致的重复请求 - 页码更新时机:必须在请求成功后再更新页码,绝对不能提前加1(否则会出现请求page=2但还没拿到数据又触发page=3的情况)
- rowHasChanged的正确实现:ListView的DataSource必须配置
rowHasChanged,确保组件能正确识别数据变化,避免重复渲染
内容的提问来源于stack exchange,提问作者Mizlul
相关产品推荐
相关产品推荐

