React useState未捕获历史状态:多服务快速选择加载状态异常
解决React状态更新时的闭包陷阱问题
嘿,我一眼就看出你遇到的问题了——这是React状态更新里非常常见的闭包陷阱导致的!咱们来一步步拆解问题,再给出解决方案。
核心问题:闭包捕获了旧的状态值
你的onSelect函数用useCallback包裹,依赖项只有selectedServices,但每次调用onSelect时,函数内部的loadingState其实是捕获的组件初始渲染时的旧状态(或者上一次useCallback更新时的状态)。
当你快速连续点击两个服务时,两次onSelect调用都是基于同一个旧的loadingState对象(也就是初始的{0: false, 1: false})进行更新:
- 第一次点击服务0:执行
setLoadingState({ ...{0:false,1:false}, 0: true }),得到{0:true,1:false} - 第二次点击服务1:还是拿着旧的
{0:false,1:false}来更新,执行后就变成{0:false,1:true},直接覆盖了第一次的更新结果,这就是为什么第一个服务的加载状态变成了false。
另外提一句,你在useEffect里的依赖项Object.keys(loadingState).map(id => id)其实没意义,因为每次状态更新这个数组都会生成新的,但本质上应该直接依赖loadingState,不过这不是核心问题。
解决方案:使用函数式更新
React的setState支持传入一个函数,这个函数会接收当前最新的状态作为参数,这样就能确保每次更新都是基于最新的状态,而不是闭包捕获的旧状态。
修改你的状态更新代码:
- 设置加载状态为true的地方,把:
setLoadingState({ ...loadingState, ...{ [id]: true } });
改成:
setLoadingState(prevState => ({ ...prevState, [id]: true }));
- 设置加载状态为false的地方,把:
setLoadingState({ ...loadingState, ...{ [id]: false } });
改成:
setLoadingState(prevState => ({ ...prevState, [id]: false }));
- 修正useEffect的依赖项(可选但推荐):
原来的依赖项不会正确触发更新,改成直接依赖loadingState:
useEffect(() => { console.log('actual loading state', loadingState); }, [loadingState]);
完整修改后的onSelect函数:
const onSelect = useCallback( async (id: string) => { const selectedServicesIds = selectedServices.map(service => service.id); setAddingInProgress(true); // 使用函数式更新获取最新状态 setLoadingState(prevState => ({ ...prevState, [id]: true })); if (selectedServicesIds.includes(id)) { selectedServicesIds.splice(selectedServicesIds.findIndex(serviceId => serviceId === id), 1); await updateServices(selectedServicesIds); } else { selectedServicesIds.push(id); await updateServices(selectedServicesIds); } setAddingInProgress(false); // 同样用函数式更新 setLoadingState(prevState => ({ ...prevState, [id]: false })); }, [selectedServices] );
为什么这样能解决问题?
函数式更新会让React在执行更新时,自动传入当前最新的状态快照。不管你的onSelect函数是何时被调用的,都能基于最新的状态进行修改,彻底避免了闭包捕获旧状态导致的覆盖问题。这样快速连续点击两个服务时,第一次更新后的状态会被第二次更新使用,最终就能得到你预期的{0: true, 1: true}啦!
内容的提问来源于stack exchange,提问作者Евгений Литвиненко
相关产品推荐
相关产品推荐

