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
相关产品推荐
相关产品推荐

