长列表分页加载时如何避免重复请求和数据错乱?
我在做商品列表的分页加载,每次滚动到底部就调用接口拉下一页数据。但有时候网络慢,用户快速滚动会触发多次请求,导致数据重复或者顺序错乱,咋办?
我试过加个 loading 锁:if (loading) return;,但偶尔还是会出现重复项,特别是用户快速上下滚动的时候。
const loadMore = async () => {
if (loading || !hasMore) return;
loading = true;
const res = await fetch(<code>/api/items?page=${page}</code>);
items.push(...res.data);
page++;
loading = false;
};
首先,除了loading状态,还应该加个abortController来取消未完成的请求:
其次,为了防止数据错乱,可以改用不可变更新方式,而不是直接push:
最后,建议加个防抖,比如用lodash的debounce或者自己简单实现:
这样三管齐下基本就能解决问题了:取消未完成请求 + 不可变更新 + 防抖。我项目里都这么搞,再没出现过重复数据的问题。
哦对了,如果你们用React的话,可以把这些逻辑封装成自定义hook,复用起来更方便。
第一,前端这边需要双重保险。除了loading状态,还要加个lastRequestId来标记最近一次请求。这样就算用户疯狂滑动,也能保证只处理最后一次请求的结果:
第二,服务端最好也做防重处理。给分页接口加个timestamp参数,超过5秒的旧请求直接拒绝。这样能避免网络延迟导致的乱序问题。
第三,实在不放心的话,可以在合并数据前做去重判断。比如商品列表可以用id检查是否已存在。不过这个算是兜底方案了,前面两个处理好基本够用。
还有个细节,page++最好放在数据合并成功之后,避免请求失败时页码错位。