如何避免React Native中FlatList调用API分页加载时出现重复数据
问题原因
- 状态更新的闭包陷阱:你在
useEffect中调用setRepos时直接使用了当前作用域捕获的repos变量,当分页请求并发返回、或者状态更新异步延迟时,repos可能还是旧值,导致新拉取的列表拼接在旧版本的repos后面,出现重复/漏项问题。 - 分页状态更新的时序问题:
setPage是异步操作,你在调用setPage(page + 1)后立刻设置loading = true,但此时page还没有完成更新,极端情况下可能出现同一页被请求多次的情况。 - 排序稳定性问题:Github搜索接口当大量仓库星标数一致时,分页的排序结果会出现漂移,也可能导致不同页返回重复条目。
修复代码
export default function App() { const [repos, setRepos] = useState([]); const [page, setPage] = useState(1); const [loading, setLoading] = useState(true); useEffect(() => { const getRepos = async () => { setLoading(true); try { const res = await axios.get( `https://api.github.com/search/repositories?q=created:>2020-01-01&sort=stars&order=desc&page=${page}&per_page=30` ); const data = res.data; // 改用函数式更新,始终基于最新的仓库列表拼接 setRepos(prevRepos => { // 新增去重逻辑,避免排序漂移导致的重复 const existIds = new Set(prevRepos.map(item => item.id)); const newItems = data.items.filter(item => !existIds.has(item.id)); return [...prevRepos, ...newItems]; }); } catch (err) { console.error('拉取仓库失败', err); } finally { setLoading(false); } }; getRepos(); }, [page]); const scrollToEnd = () => { if (!loading) { // 页码更新也用函数式写法,确保拿到最新值 setPage(prevPage => prevPage + 1); } }; const renderItem = ({ item }) => ( <Text className="boxed"> Stars: {item.stargazers_count} Id: {item.id} </Text> ); return ( <SafeAreaView style={styles.screen}> <View style={styles.container}> <FlatList data={repos} renderItem={renderItem} keyExtractor={(item) => item.id.toString()} // 确保key为字符串,避免潜在异常 onEndReached={scrollToEnd} onEndReachedThreshold={0.5} // 滚动到距离底部50%可视区域时触发加载,减少误触发 showsVerticalScrollIndicator={false} /> {loading && <Text className="loading">...loading</Text>} </View> <StatusBar style="auto" /> </SafeAreaView> ); }
核心修改点
- 把
setRepos改为函数式更新,避免闭包捕获旧状态导致的拼接错误 - 新增基于仓库ID的去重逻辑,解决Github搜索接口排序不稳定导致的重复条目问题
- 调整
loading的更新时机,放到请求的finally块中,避免请求失败后加载状态锁死 setPage也改为函数式更新,确保拿到的是最新的页码值- 给
keyExtractor的返回值转为字符串,避免数字类型ID作为key的潜在异常 - 增加
onEndReachedThreshold参数,控制加载更多的触发频率,减少误触发
内容的提问来源于stack exchange,提问作者taha maatof
相关产品推荐
相关产品推荐

