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

React异步setState后无法立即获取新值?求原因及解决方案

问题:React异步setState后await仍获取旧值

我自定义了useStateCallback Hook,让setState支持第二个参数回调函数,通过Promise包装后用await等待setState完成,但执行await clearEnhanceSwitch()后,console.log(videoSwitchs, audioSwitches)仍输出旧值,想知道该方案不可行的原因,以及对应的解决方案。

相关代码如下:

type CallbackFunction<T> = (state: T) => void;

export default function useStateCallback<T>(init: T): [T, (newValue: T, callback?: CallbackFunction<T>) => void] {
  const [state, setState] = useState<T>(init);
  const callbackRef = useRef<CallbackFunction<T> | null>(null);

  useEffect(() => {
    console.log(state);
    if (callbackRef.current) {
      callbackRef.current(state);
      // callbackRef.current = null;
    }
  }, [state]);

  const setStateWithCallback = useCallback((newValue: T, callback?: CallbackFunction<T>) => {
    setState(newValue);
    if (callback) {
      callbackRef.current = callback;
    }
  }, []);

  return [state, setStateWithCallback];
};

const [videoSwitchs, setVideoSwitches] = useStateCallback<VideoEnhanceField[]>(['superResolution']);
const [audioSwitches, setAudioSwitches] = useStateCallback<AudioEnhanceField[]>([]);
const clearEnhanceSwitch = () => {
    const stack = [];
    if (!options.value.includes('video')) {
      stack.push(
        new Promise((resolve) => {
          setVideoSwitches([], () => {
            resolve(true);
          });
        })
      );
    }
    if (!options.value.includes('audio')) {
      stack.push(
        new Promise((resolve) => {
          setAudioSwitches([], () => {
            resolve(true);
          });
        })
      );
    }
    return Promise.all(stack);
};

async function submit() {
    if (!canSubmit) {
      return;
    }
    // setSubmitLoading(true);

    await clearEnhanceSwitch();
    console.log(videoSwitchs, audioSwitches);  // 输出旧值
}

原因分析

  • 闭包陷阱:submit函数在组件某次渲染时创建,它捕获的videoSwitchs和audioSwitches是该次渲染的旧值。即便状态更新触发了新渲染,当前执行的submit函数闭包不会更新,await后的console.log依然使用之前捕获的旧变量。
  • 状态更新的异步性:React状态更新是异步批量处理的,await clearEnhanceSwitch()仅等待回调触发Promise resolve,但此时组件还未完成重新渲染,新的状态变量还未生成,submit里的变量还是旧值。
  • 自定义Hook的回调逻辑:你的useStateCallback中回调在useEffect执行,确实能拿到新状态,但Promise resolve后,submit函数的闭包没有更新,依然访问旧的状态变量。

解决方案

方案1:在Promise中返回新状态

修改clearEnhanceSwitch,在回调中保存新状态,最后通过Promise返回:

const clearEnhanceSwitch = () => {
  const stack = [];
  let newVideoSwitches = videoSwitchs;
  let newAudioSwitches = audioSwitches;

  if (!options.value.includes('video')) {
    stack.push(
      new Promise((resolve) => {
        setVideoSwitches([], (newState) => {
          newVideoSwitches = newState;
          resolve(true);
        });
      })
    );
  }
  if (!options.value.includes('audio')) {
    stack.push(
      new Promise((resolve) => {
        setAudioSwitches([], (newState) => {
          newAudioSwitches = newState;
          resolve(true);
        });
      })
    );
  }
  return Promise.all(stack).then(() => ({
    videoSwitchs: newVideoSwitches,
    audioSwitches: newAudioSwitches
  }));
};

在submit中接收返回的新状态:

async function submit() {
  if (!canSubmit) return;

  const { videoSwitchs: newVideo, audioSwitches: newAudio } = await clearEnhanceSwitch();
  console.log(newVideo, newAudio); // 输出新值
}

方案2:用useRef保存最新状态

通过ref实时同步状态,ref.current始终指向最新值,不受闭包影响:

const [videoSwitchs, setVideoSwitches] = useStateCallback<VideoEnhanceField[]>(['superResolution']);
const [audioSwitches, setAudioSwitches] = useStateCallback<AudioEnhanceField[]>([]);

// 定义ref保存最新状态
const videoSwitchsRef = useRef(videoSwitchs);
const audioSwitchesRef = useRef(audioSwitches);

// 状态更新时同步到ref
useEffect(() => {
  videoSwitchsRef.current = videoSwitchs;
  audioSwitchesRef.current = audioSwitches;
}, [videoSwitchs, audioSwitches]);

async function submit() {
  if (!canSubmit) return;

  await clearEnhanceSwitch();
  console.log(videoSwitchsRef.current, audioSwitchesRef.current); // 输出最新值
}

方案3:优化自定义Hook,让setState返回Promise

修改useStateCallback,让setStateWithCallback直接返回Promise,无需手动包装,同时直接拿到新状态:

type CallbackFunction<T> = (state: T) => void;

export default function useStateCallback<T>(init: T): [T, (newValue: T, callback?: CallbackFunction<T>) => Promise<T>] {
  const [state, setState] = useState<T>(init);
  const callbackRef = useRef<CallbackFunction<T> | null>(null);
  const resolveRef = useRef<((value: T) => void) | null>(null);

  useEffect(() => {
    if (callbackRef.current) {
      callbackRef.current(state);
      callbackRef.current = null;
    }
    if (resolveRef.current) {
      resolveRef.current(state);
      resolveRef.current = null;
    }
  }, [state]);

  const setStateWithCallback = useCallback((newValue: T, callback?: CallbackFunction<T>) => {
    setState(newValue);
    if (callback) {
      callbackRef.current = callback;
    }
    // 返回Promise,状态更新后resolve新值
    return new Promise<T>((resolve) => {
      resolveRef.current = resolve;
    });
  }, []);

  return [state, setStateWithCallback];
};

修改clearEnhanceSwitch直接调用返回Promise的setState:

const clearEnhanceSwitch = () => {
  const stack = [];
  if (!options.value.includes('video')) {
    stack.push(setVideoSwitches([]));
  }
  if (!options.value.includes('audio')) {
    stack.push(setAudioSwitches([]));
  }
  return Promise.all(stack);
};

在submit中直接获取返回的新状态:

async function submit() {
  if (!canSubmit) return;

  const [newVideo, newAudio] = await clearEnhanceSwitch();
  console.log(newVideo, newAudio); // 输出新值
}

内容的提问来源于stack exchange,提问作者user22402589

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.13 03:27:02