本文解析 react 中 usememo 依赖数组对象更新后未触发重新计算的问题,核心在于异步操作中错误使用 foreach 导致状态更新时机错乱,并提供基于 promise.all 的正确异步批量处理方案。
本文解析 react 中 usememo 依赖数组对象更新后未触发重新计算的问题,核心在于异步操作中错误使用 foreach 导致状态更新时机错乱,并提供基于 promise.all 的正确异步批量处理方案。
在 React 函数组件中,useMemo 的执行严格依赖其依赖数组(deps)的引用变化。当 asyncProcesses 是一个由 useState 管理的数组时,useMemo 只有在该数组变量本身发生新引用赋值(如 [...oldArray] 或 array.map(...) 返回新数组)且该新数组被 setAsyncProcesses 同步设置后,才会重新执行。然而,问题代码中存在一个关键陷阱:在未等待所有异步操作完成前就调用了 setAsyncProcesses。
具体来看,原逻辑使用了:
asyncProcesses.forEach(async (process, i) => { const newProcess = await checkUploadCsvProcess(/* ... */); newAsyncProcesses.splice(i, 1, newProcess); // ✅ 修改副本});setAsyncProcesses(newAsyncProcesses); // ❌ 过早调用!此时 newProcess 尚未全部 resolve
Array.prototype.forEach 是同步方法,即使回调是 async,它也不会等待 Promise 完成——它只是立即启动所有异步任务并继续执行下一行(即 setAsyncProcesses)。这意味着 newAsyncProcesses 在多数 newProcess 仍处于 pending 状态时就被设为新状态,导致 asyncProcesses 实际更新为一个部分未完成、字段仍为旧值(如 'uploading')的中间态数组。useMemo 拿到的就是这个“过期”数组,自然无法反映最终的 'success' 状态。
✅ 正确做法是:先并发发起所有请求,再统一等待全部完成,最后一次性更新状态。推荐使用 Promise.all 配合 map:
const updateProcesses = async () => { try { // 并发执行所有检查,返回 Promise<AsyncProcess[]> 数组 const updatedProcesses = await Promise.all( asyncProcesses.map(async (process, i) => { return checkUploadCsvProcess( process as AsyncProcessCsvUpload, { actionName: dictionary.review, actionOnClick: () => { navigate(routes.linkedAccounting); setIsOpenInDialog(AsyncProcessType.CsvUpload, true); }, } ); }) ); // 过滤掉 undefined(如 catch 中返回),确保类型安全 const validProcesses = updatedProcesses.filter(Boolean) as AsyncProcess[]; // 一次性设置最新状态 setAsyncProcesses(validProcesses); } catch (error) { console.error('Failed to update async processes:', error); }};// 调用此函数替代原 forEach 逻辑updateProcesses();
⚠️ 注意事项:
return { ...process, snackbarType: 'success', isActive: false, // ...其他覆盖字段};
总结:useMemo 不刷新的本质不是 Hook 本身失效,而是状态更新逻辑存在竞态条件。修复关键在于将异步操作从“火球式并发+提前提交”改为“并发发起+聚合等待+原子提交”。这一模式不仅解决 useMemo 陈旧数据问题,也提升了状态一致性与可预测性,是 React 异步状态管理的最佳实践之一。