异步函数中useState无法更新状态变量的问题排查
问题分析与解决方案
核心问题
- 闭包导致状态读取异常:异步循环中,
choices始终是函数初始化时的旧值,循环结束后直接访问会拿到初始空数组,这就是控制台打印undefined的原因。 - 对象数组去重逻辑错误:
new Set(choices)无法对对象数组去重,因为对象是引用类型,每个对象的引用地址不同,Set会判定它们都是唯一值。 - 多次 setState 引发的异常:循环内每次匹配都调用
setChoices,不仅会触发多次组件重渲染,还因状态更新的异步特性,可能导致状态合并时出现异常。
修复后的代码
const getChoices = async () => { try { setIsLoading(true); // 用临时数组统一收集结果,避免多次调用setState const newChoices = []; for (let i = 0; i < 1387; i++) { for (let j = 0; j < days.length; j++) { if (moment(data[i].date.start).utc().format("MM/DD/YYYY") === days[j]) { const kli = await Contract.methods .GetChoice(data[i].id) .call({ from: Account[0] }); console.log(kli); if (kli !== "0") { const homeId = data[i].teams.home.id; const visitorId = data[i].teams.visitors.id; const parsedKli = parseInt(kli); if (parsedKli === homeId) { console.log("inserted in home"); console.log(data[i].id, data[i].teams.home.nickname); newChoices.push({ MatchID: data[i].id, BetTeamName: data[i].teams.home.nickname, }); } else if (parsedKli === visitorId) { console.log("inserted in vistiroes"); newChoices.push({ MatchID: data[i].id, BetTeamName: data[i].teams.visitors.nickname, }); } } } } } // 根据MatchID实现对象数组去重 const uniqueChoices = Array.from( new Map(newChoices.map(item => [item.MatchID, item])).values() ); setChoices(uniqueChoices); console.log(uniqueChoices); // 此处可正确打印最终结果 } finally { setIsLoading(false); } };
关键优化点
- 临时数组统一收集:先将所有符合条件的条目存入
newChoices,最后一次性调用setChoices,既避免了频繁重渲染,也解决了闭包无法读取最新状态的问题。 - 正确的对象去重方式:利用
Map以MatchID作为键存储条目,自动覆盖重复ID的条目,再转换为数组完成去重,适配对象数组的场景。 - 简化条件判断逻辑:提取重复变量,让代码结构更清晰,降低出错概率。
内容的提问来源于stack exchange,提问作者wasee
相关产品推荐
相关产品推荐

