React组件首次点击calculate按钮报Cannot read properties of undefined错误
React setState异步导致首次点击calculate报错的解决方案
错误原因
- React的
setState是异步操作,调用setUsersName和setUsers后,组件状态不会立刻更新 - 首次点击
calculate时,retribute函数里使用的usersNames和users还是更新前的旧状态,导致通过indexOf查找位置后,取出的debtor或creditor为undefined,访问balance时触发TypeError - 第二次点击时,状态已经完成更新,所以能正常获取到用户数据
解决方案
直接在calculate函数中传递最新的用户数据给retribute,不要依赖还未更新的组件状态,具体修改如下:
1. 修改calculate函数
const calculate = () => { const total = users.reduce((acc, user) => acc + user.amount, 0); const perPerson = total / users.length; const newUsers = users.map(user => { return {...user, balance: user.amount - perPerson, transactions: []}; }); const currentUserNames = newUsers.map(user => user.name); // 直接从newUsers获取最新名称列表 setTotal(total); setUsersName(currentUserNames); setUsers(newUsers); const {debtors, creditors} = distribute(newUsers); retribute(newUsers, currentUserNames, debtors, creditors); // 传入最新的用户数据和名称 };
2. 修改retribute函数
const retribute = (currentUsers, currentUserNames, debtorsList, creditorsList) => { const usersUpdated = [...currentUsers]; // 复制最新用户数组,遵循React状态不可变原则 debtorsList.forEach(debtorName => { const debtorPos = currentUserNames.indexOf(debtorName); const debtor = usersUpdated[debtorPos]; creditorsList.forEach(creditorName => { if (debtor.balance < 0) { const creditorPos = currentUserNames.indexOf(creditorName); const creditor = usersUpdated[creditorPos]; if (creditor.balance >= Math.abs(debtor.balance)) { creditor.balance -= Math.abs(debtor.balance); debtor.transactions.push(`${debtor.name} gives ${creditor.name}: ${Math.abs(debtor.balance)}`); debtor.balance = 0; } else { debtor.balance += creditor.balance; debtor.transactions.push(`${debtor.name} gives ${creditor.name}: ${creditor.balance}`); creditor.balance = 0; } } }); }); console.log(usersUpdated); // 若需要将计算结果同步到组件状态,可在此调用setUsers(usersUpdated) };
额外优化点
- 用
Math.abs()替代*-1,代码可读性更强 - 复制用户数组避免直接修改原数据,符合React状态不可变的设计理念
内容的提问来源于stack exchange,提问作者stefano
相关产品推荐
相关产品推荐

