React Game Provider变量在逻辑函数中无法更新的问题
问题分析与解决方案
问题核心
你遇到的问题是:initializeGame函数里拿到的gameEngine对象是组件第一次渲染时的快照,后续React状态更新后,这个对象里的isReady、text始终是旧值,导致轮询永远无法检测到状态变化。
之前多轮测试无效的原因:
getReady函数:因为闭包陷阱,它捕获的是第一次渲染时的isReady状态,后续状态更新后,函数依然返回初始的false。- 对象包裹
isReady:第一次渲染时gameEngine里的对象是初始引用,后续状态更新时组件会创建新的gameEngine对象,但initializeGame里持有的是旧对象,旧对象的value从未被更新。 useRef存储isReady:如果只是把isReady改成Ref,gameEngine里的isReady是ref.current的初始快照(false),后续ref.current变化后,旧gameEngine里的isReady依然是旧值,除非用Ref保存整个gameEngine的最新引用。
根本原因:
当setReady(true)触发GameEngineProvider重新渲染时,组件会创建一个全新的gameEngine对象,但initializeGame里持有的是第一次渲染时传入的旧对象。而setText能正常工作,是因为React的setState函数是稳定的,不会随组件重新渲染而变化,旧对象里的setText依然能触发状态更新。
解决方案
方案1:用Ref保存最新的GameEngine引用
通过useRef保存最新的gameEngine对象,让initializeGame能随时访问到最新状态:
修改GameEngineProvider代码:
export const GameEngineProvider = ({ children }: PropsWithChildren): JSX.Element => { // Engine States const isLoaded = useRef<boolean>(false); const [isReady, setReady] = useState<boolean>(false); const [text, setText] = useState<string>(""); // Example of other hook provided by GameEngine const player = usePlayer(); // 用Ref保存最新的gameEngine对象 const gameEngineRef = useRef<GameEngineContextType>({} as GameEngineContextType); // 每次渲染都更新Ref,确保它指向最新的状态和函数 const gameEngine: GameEngineContextType = { isReady, getReady: () => isReady, text, setText, updateGameStep: () => { /* Go to the next game step */ }, player, }; gameEngineRef.current = gameEngine; useEffect(() => { if (!isLoaded.current) { isLoaded.current = true; // 传入Ref而不是静态对象 initializeGame(gameEngineRef); setTimeout(() => { console.log("SET Ready"); setReady(true); }, 2000); } }, []); return ( <GameEngineContext.Provider value={gameEngine}> {children} </GameEngineContext.Provider> ); };
修改initializeGame和waitForCondition,从Ref里获取最新状态:
export const initializeGame = async (gameEngineRef: React.MutableRefObject<GameEngineContextType>) => { gameEngineRef.current.setText("Loading..."); // Wait GameEngine to be ready await waitForCondition(() => { const latestEngine = gameEngineRef.current; console.log("Is game ready: ", latestEngine.text, latestEngine.isReady, latestEngine.getReady()); return latestEngine.isReady; }); // Call function to go on the next step gameEngineRef.current.updateGameStep(); };
方案2:将initializeGame改为组件内部函数
直接在GameEngineProvider内部定义initializeGame,让它能直接访问组件内的最新状态,避免闭包问题:
export const GameEngineProvider = ({ children }: PropsWithChildren): JSX.Element => { // Engine States const isLoaded = useRef<boolean>(false); const [isReady, setReady] = useState<boolean>(false); const [text, setText] = useState<string>(""); // Example of other hook provided by GameEngine const player = usePlayer(); // 内部定义initializeGame,直接访问最新状态 const initializeGame = async () => { setText("Loading..."); await waitForCondition(() => { console.log("Is game ready: ", text, isReady); return isReady; }); updateGameStep(); }; const updateGameStep = () => { /* Go to the next game step */ }; useEffect(() => { if (!isLoaded.current) { isLoaded.current = true; initializeGame(); setTimeout(() => { console.log("SET Ready"); setReady(true); }, 2000); } }, []); const gameEngine: GameEngineContextType = { isReady, getReady: () => isReady, text, setText, updateGameStep, player, }; return ( <GameEngineContext.Provider value={gameEngine}> {children} </GameEngineContext.Provider> ); };
验证效果
修改后,initializeGame里的轮询会正确检测到isReady变为true,日志会输出:
Is game ready: Loading... false false Is game ready: Loading... false false SET Ready Is game ready: Loading... true true
同时GameData组件依然能正常显示状态变化。
内容的提问来源于stack exchange,提问作者Arthur
相关产品推荐
相关产品推荐

