React Native频繁调用API致卡顿、列表更新异常优化方案
问题根因
你遇到的列表更新不及时问题,核心原因不是请求并发阻塞,是两处代码逻辑错误:
- 异步调用时序错误:新增商品的点击逻辑里,没有等
submitForm()的新增接口执行完成,就立刻调用了getUserProducts()拉列表。这时候后端还没完成新商品入库,拉到的自然是没包含新商品的旧数据。等你下一次操作再触发拉取时,上一次的新增早就写入完成了,就会出现“上次加的商品这次才显示”的现象。
你现在的新增按钮逻辑问题很明显:onPress={() => { setVisible(false); submitForm(); // 异步函数没加await,请求发出去就直接往下走 console.log('added'); getUserProducts(); // 执行到这里时,新增请求大概率还没跑完 }} - 重复请求引发竞态:连续增删操作时短时间触发多次列表拉取请求,后发的请求可能比先发的更早返回,旧请求返回后会把新状态覆盖成旧数据,导致展示异常。
可行优化方案
按改造成本从低到高选就行,组合用效果最好:
- 第一步先修正时序问题:所有增/删操作完成后,等接口返回成功再拉取最新列表。新增按钮的逻辑要改成async,等submitForm执行成功再调getUserProducts,删除逻辑你现在已经写了await,是对的,新增漏了await而已。
改完的新增点击逻辑示例:
对应的submitForm也要补返回值,不要成功了啥也不返回:onPress={async () => { setVisible(false); // 等待新增接口成功返回 const addSuccess = await submitForm(); if (addSuccess) { console.log('added'); getUserProducts(); } }}const submitForm = async () => { const body = { product_id: productId, product: product, num: num, amount: amount, }; const response = await postProduct(body); // 新增成功返回true,失败返回false return response !== undefined; }; - 第二步加请求防抖+重复请求取消:给
getUserProducts加300ms左右的防抖,短时间内多次触发只会最后发一次请求,同时如果上一次拉列表的请求还没完成,就取消上一次的请求,避免旧请求返回覆盖新数据。
最简实现可以用ref存标记位:// 加两个ref存请求取消控制器和防抖定时器 const getListAbortRef = useRef<AbortController | null>(null); const getListTimerRef = useRef<NodeJS.Timeout | null>(null); const getUserProducts = () => { // 清掉之前的防抖定时器 if (getListTimerRef.current) clearTimeout(getListTimerRef.current); // 取消上一次还在传输的请求 if (getListAbortRef.current) getListAbortRef.current.abort(); getListTimerRef.current = setTimeout(async () => { const controller = new AbortController(); getListAbortRef.current = controller; try { const response = await axios.get( 'http://192.168.1.32:3000/api/user_products', { signal: controller.signal } ); setUserProducts(response.data); } catch (error) { // 忽略主动取消的请求报错 if (!axios.isCancel(error)) { alert('获取列表失败'); } } finally { getListAbortRef.current = null; } }, 300); }; - 第三步做本地乐观更新(体验最优):增删操作根本不需要等接口返回再重新拉全量列表,操作的时候直接本地更新
userProducts状态,接口在后台静默执行就行,既没有请求并发问题,用户操作也没有等待感。
比如新增成功后直接往数组里塞新条目:
删除的时候直接过滤掉对应id的条目:// submitForm成功后不用拉全量列表,直接本地更新 const newItem = { product_id: productId, product, num, amount }; setUserProducts(prev => [...prev, newItem]);
这种改法完全避免了频繁拉列表的性能损耗,就算偶尔接口请求失败,加个简单的失败回滚逻辑就行——比如新增失败就把刚塞进去的条目删掉,给个错误提示,完全不影响使用。onPress={async () => { // 先本地更新,用户立刻看到删除效果 setUserProducts(prev => prev.filter(item => item.product_id !== userProduct.product_id)); await deleteProduct(userProduct.product_id); ToastAndroid.show('Done', ToastAndroid.SHORT); // 怕本地状态和后端不一致的话,可以隔几秒后台静默拉一次同步,不用等接口返回再刷新UI }}
额外注意点
- 你初始useEffect里的
console.log(userProducts)打出来的永远是初始空值,因为state更新是异步的,effect执行的时候还没拿到更新后的状态,这个log没用可以删掉。 - 现在所有列表项的CheckBox都绑定了同一个
toggleCheckBox状态,点一个所有框都会跟着变,这个也是待修复的逻辑问题,需要给每个商品单独存选中状态。
内容的提问来源于stack exchange,提问作者Grand Skunk
相关产品推荐
相关产品推荐

