You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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支持传入一个函数,这个函数会接收当前最新的状态作为参数,这样就能确保每次更新都是基于最新的状态,而不是闭包捕获的旧状态。

修改你的状态更新代码:

  1. 设置加载状态为true的地方,把:
setLoadingState({ ...loadingState, ...{ [id]: true } });

改成:

setLoadingState(prevState => ({ ...prevState, [id]: true }));
  1. 设置加载状态为false的地方,把:
setLoadingState({ ...loadingState, ...{ [id]: false } });

改成:

setLoadingState(prevState => ({ ...prevState, [id]: false }));
  1. 修正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,提问作者Евгений Литвиненко

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.14 07:23:45