React中状态更新未在异步WaitFor函数中生效的问题求助
问题原因分析
你遇到的是React状态的闭包陷阱:当waitForUnityInstanceToStart被调用时,内部的poll函数会捕获调用瞬间的unityInstanceIsStarted值(此时是false)。后续setUnityInstanceIsStarted(true)触发组件重新渲染后,会生成新的unityInstanceIsStarted变量,但之前已经启动的poll函数仍然引用旧的状态值,所以永远不会检测到状态变为true。
解决方案
方案1:使用useRef同步状态(最简单的轮询改进)
useRef的.current属性是可变的,闭包能始终访问到最新值,不会受组件重新渲染的影响:
import { useState, useRef, useEffect } from 'react'; const [unityInstanceIsStarted, setUnityInstanceIsStarted] = useState(false); // 用ref同步状态,确保闭包能拿到最新值 const unityInstanceStartedRef = useRef(false); useEffect(() => { // 状态更新时同步ref的值 unityInstanceStartedRef.current = unityInstanceIsStarted; }, [unityInstanceIsStarted]); function waitForUnityInstanceToStart() { return new Promise((resolve) => { const poll = () => { if (unityInstanceStartedRef.current) { resolve(); } else { setTimeout(poll, 500); } }; // 立即执行一次检查 poll(); }); } const waitForUnityInstanceToOpenProject = async (projectID) => { setEditorView(EditorView.Loading); await waitForUnityInstanceToStart(); sendMessage(reactUnityRelay, "OpenProject", projectID); };
方案2:用Promise订阅替代轮询(更高效)
避免定时轮询,直接监听状态变化触发Promise resolve,性能更好:
import { useState, useRef, useEffect } from 'react'; const [unityInstanceIsStarted, setUnityInstanceIsStarted] = useState(false); // 保存Promise的resolve函数引用 const unityReadyResolver = useRef(null); // 缓存已创建的Promise,避免重复创建 const unityReadyPromise = useRef(null); useEffect(() => { // 当Unity实例启动且有等待的resolve时,触发完成 if (unityInstanceIsStarted && unityReadyResolver.current) { unityReadyResolver.current(); unityReadyResolver.current = null; unityReadyPromise.current = null; // 重置缓存,下次调用重新创建 } }, [unityInstanceIsStarted]); function waitForUnityInstanceToStart() { // 如果已经启动,直接返回已完成的Promise if (unityInstanceIsStarted) { return Promise.resolve(); } // 如果已有等待的Promise,直接返回 if (unityReadyPromise.current) { return unityReadyPromise.current; } // 创建新的Promise并缓存 unityReadyPromise.current = new Promise((resolve) => { unityReadyResolver.current = resolve; }); return unityReadyPromise.current; } const waitForUnityInstanceToOpenProject = async (projectID) => { setEditorView(EditorView.Loading); await waitForUnityInstanceToStart(); sendMessage(reactUnityRelay, "OpenProject", projectID); };
额外优化:清理定时器(针对方案1)
如果用户可能在等待过程中取消操作,需要清理定时器避免内存泄漏,可以用AbortController:
function waitForUnityInstanceToStart(signal) { return new Promise((resolve, reject) => { let timerId; const poll = () => { // 如果信号已中止,拒绝Promise并清理定时器 if (signal.aborted) { reject(new Error("Wait aborted")); return; } if (unityInstanceStartedRef.current) { resolve(); clearTimeout(timerId); } else { timerId = setTimeout(poll, 500); } }; poll(); // 监听中止信号,清理定时器 signal.addEventListener('abort', () => { clearTimeout(timerId); }); }); } // 使用示例 const waitForUnityInstanceToOpenProject = async (projectID) => { setEditorView(EditorView.Loading); const abortController = new AbortController(); try { await waitForUnityInstanceToStart(abortController.signal); sendMessage(reactUnityRelay, "OpenProject", projectID); } catch (err) { console.log(err.message); } // 组件卸载时可以调用abortController.abort()清理 };
内容的提问来源于stack exchange,提问作者jmajetich
相关产品推荐
相关产品推荐

