React中API调用前后setState并发执行导致状态异常的解决方法
解决React中setState异步更新的竞态问题
问题根源
你遇到的问题本质是React的setState是异步批量更新机制:快速点击多个按钮时,updateUserAccessLoadingState被多次调用,每次读取的isUpdatingUserAccess都是更新前的旧状态快照,导致后续更新覆盖了之前的操作,最终状态出现异常。
解决方案
1. 用函数式setState修复核心问题
把直接依赖当前state的更新逻辑,改成接收最新状态的函数式更新,确保每次更新都基于最新的状态快照:
修改updateUserAccessLoadingState函数:
const updateUserAccessLoadingState = (id, loading) => { setIsUpdatingUserAccess(prevState => { const existingIndex = prevState.findIndex(el => el.requestTypeId === id); if (existingIndex !== -1) { // 复制原数组并修改对应条目(避免直接修改原状态) const newState = [...prevState]; newState[existingIndex] = { ...newState[existingIndex], loading }; return newState; } else { // 添加新的状态条目 return [...prevState, { requestTypeId: id, loading }]; } }); };
函数式更新会让React传入当前最新的prevState,彻底避免异步更新导致的状态覆盖问题。
2. 防止重复点击(优化体验+减少无效操作)
在API请求未完成时,禁止对应按钮的重复点击,避免不必要的状态更新和API调用:
修改updateRequestAccess函数:
const updateRequestAccess = (requestId, checked, user) => { // 检查当前id是否正在加载,是则直接返回 const isLoading = isUpdatingUserAccess.some(el => el.requestTypeId === requestId && el.loading); if (isLoading) return; updateUserAccessLoadingState(requestId, true); someApi.put(requestId, user, checked).finally(() => { // 无论请求成功失败,都取消加载状态(用finally更简洁) updateUserAccessLoadingState(requestId, false); }); };
同时在按钮组件中根据状态禁用:
<button onClick={() => updateRequestAccess(id, checked, user)} disabled={isUpdatingUserAccess.some(el => el.requestTypeId === id && el.loading)} > {isUpdatingUserAccess.some(el => el.requestTypeId === id && el.loading) ? '加载中...' : '切换权限'} </button>
3. 优化状态结构(可选,提升性能)
把数组形式的状态改成对象(以requestTypeId为key),查找和修改效率更高,代码更简洁:
初始化状态:
const [isUpdatingUserAccess, setIsUpdatingUserAccess] = useState({});
修改updateUserAccessLoadingState:
const updateUserAccessLoadingState = (id, loading) => { setIsUpdatingUserAccess(prevState => ({ ...prevState, [id]: loading })); };
判断加载状态时直接用:
const isLoading = isUpdatingUserAccess[requestId];
总结
优先用函数式setState解决异步更新的竞态问题,再结合按钮禁用防止重复操作,最后可以根据需求优化状态结构提升代码效率。
内容的提问来源于stack exchange,提问作者SmalliSax
相关产品推荐
相关产品推荐

