
本文解析了 React 中 useMemo 未响应数组对象状态更新的根本原因——异步操作未正确等待 Promise 完成,导致 setAsyncProcesses 提前触发、依赖数组引用未更新,并提供基于 Promise.all 的可靠修复方案。
本文解析了 react 中 `usememo` 未响应数组对象状态更新的根本原因——异步操作未正确等待 promise 完成,导致 `setasyncprocesses` 提前触发、依赖数组引用未更新,并提供基于 `promise.all` 的可靠修复方案。
在 React 函数组件中,useMemo 的执行严格依赖其依赖数组(deps)中值的引用变化。当 asyncProcesses 是一个由 useState 管理的对象数组时,useMemo 只有在 asyncProcesses 引用发生改变(即 setAsyncProcesses 被调用且传入新数组)时才会重新计算。然而,问题代码中存在一个关键陷阱:
asyncProcesses.forEach(async (process, i) => {
const newProcess = await checkUploadCsvProcess(/* ... */);
newAsyncProcesses.splice(i, 1, newProcess); // ❌ 副作用式修改
});
setAsyncProcesses(newAsyncProcesses); // ⚠️ 过早调用!此处 forEach 是同步方法,它会立即遍历并启动所有 checkUploadCsvProcess(返回 Promise),但不会等待任何 Promise 结束。因此 setAsyncProcesses 在所有异步操作完成前就被执行,此时 newAsyncProcesses 中的元素仍为旧引用(例如 snackbarType 仍为 'uploading'),导致 useMemo 接收到的 asyncProcesses 并非最终更新后的版本。
✅ 正确做法:使用 Promise.all 确保状态更新时机准确
应将并发异步操作收集为 Promise 数组,并用 Promise.all 统一等待全部完成后再更新状态:
使用 @ainative/react-sdk 为 React 应用添加 AI 聊天和积分。适用于 (1) 安装 @ainative/react-sdk,(2) 使用 useChat hook 实现聊天完成。
const updateProcesses = async () => {
try {
// 1. 并发执行所有检查,返回 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);
},
}
);
})
);
// 2. 过滤掉 undefined(如 catch 中返回),生成全新数组
const validProcesses = updatedProcesses.filter(Boolean) as AsyncProcess[];
// 3. 使用新数组触发重渲染 → useMemo 将收到最新引用
setAsyncProcesses(validProcesses);
} catch (error) {
console.error('Failed to update async processes:', error);
}
};? 关键点说明:
- map + Promise.all 确保所有异步逻辑完成后再进入 setAsyncProcesses;
- validProcesses 是一个全新数组引用,包含已更新字段(如 snackbarType: 'success')的对象,满足 useMemo 依赖更新条件;
- 避免直接修改原对象属性(如 process.snackbarType = 'success'),虽可工作,但违背不可变原则,易引发隐性 bug;推荐在 checkUploadCsvProcess 内部返回新对象(见下方优化建议)。
? 进阶建议:保持不可变性,提升可预测性
当前 checkUploadCsvProcess 直接修改传入对象(如 process.snackbarType = 'success'),这属于突变(mutation),虽能“生效”,但破坏了 React 状态管理的最佳实践。更健壮的写法是返回一个新对象:
// ✅ 推荐:纯函数式更新,返回新对象
export const checkUploadCsvProcess = async (
process: AsyncProcessCsvUpload,
reviewAction: { actionName: string; actionOnClick: () => void }
): Promise<AsyncProcessCsvUpload | undefined> => {
try {
const integration = await integrationsClient.getIntegration(process.data.integrationId);
const { logs, status } = integration;
// 构建新 process 对象,不修改原对象
const baseUpdate = {
...process,
timesChecked: (process.timesChecked || 0) + 1,
};
if (status === IntegrationStatus.UPLOAD) {
return {
...baseUpdate,
isActive: true,
progress: Math.round((logs?.info?.progress || 0) / (logs?.info?.transactionCount || 1) * 100),
};
} else if (status === IntegrationStatus.ERROR) {
return {
...baseUpdate,
isActive: false,
snackbarType: 'error',
snackbarAction: reviewAction,
data: { ...process.data, errors: logs?.error || {} },
};
} else {
return {
...baseUpdate,
isActive: false,
snackbarType: 'success',
snackbarAction: reviewAction,
};
}
} catch (error) {
console.error('checkUploadCsvProcess failed:', error);
return undefined;
}
};? 总结
- useMemo 不更新 ≠ Hook 本身有问题,而是其依赖项(asyncProcesses)未真正更新;
- forEach(async () => {}) 不等待 Promise,是常见并发陷阱;
- 必须使用 Promise.all(array.map(...)) 确保异步操作全部完成后再调用 setState;
- 配合不可变更新(返回新对象而非修改原对象),可彻底规避引用陈旧与意外突变风险,使 useMemo、useEffect 等依赖追踪机制稳定可靠。

















