非React函数中使用Redux:同步服务端与客户端状态问题
我懂你遇到的痛点了——Redux Hooks确实只能在React函数组件或者自定义Hook里用,普通函数直接调用肯定会报错。结合你的需求(定期对比服务端和客户端状态,不同就更新客户端),给你两个可行的解决方案:
方案一:把逻辑移到React组件中(推荐)
既然你本来就打算在MainView组件里调用这个检查逻辑,那直接把对比和更新的逻辑放到组件里,利用useEffect来实现定时调用,同时在组件里正常使用useSelector和useDispatch就好。
修改后的MainView代码示例:
import React, { useEffect } from 'react'; import '../../style/App.scss'; import Wrapper from '../wrapper/wrapper'; import StartView from '../startview/StartView'; import { useSelector, useDispatch } from 'react-redux'; // 假设你有一个更新gameState的action creator import { updateGameState } from '../path/to/your/actions'; async function getGamestate() { const gameStates = await fetch('http://localhost:3000/game-state').then(response => response.json()); return { data: gameStates }; } function MainView(){ const gameState = useSelector(state => state.gameState); const dispatch = useDispatch(); // 定义检查并更新的逻辑 const checkAndUpdateState = async () => { const serverGameState = await getGamestate(); // 注意:如果gameState是对象,直接用!==会判断引用是否相同,建议用深比较 // 可以用JSON.stringify或者lodash的isEqual if (JSON.stringify(serverGameState.data) !== JSON.stringify(gameState)) { console.log('状态已更新,同步客户端'); dispatch(updateGameState(serverGameState.data)); } else { console.log('状态未变化'); } }; // 用useEffect设置定时调用,组件挂载后开始,卸载时清理定时器 useEffect(() => { // 立即调用一次 checkAndUpdateState(); // 每5秒调用一次 const intervalId = setInterval(checkAndUpdateState, 5000); // 清理函数 return () => clearInterval(intervalId); }, [gameState, dispatch]); switch (gameState) { case "notStarted": return <StartView/>; case "video": case "keypad": case "puzzle": case "completed": case "failed": return <Wrapper/>; default: return ( <div className="contentArea"> <h1>Er is een fout opgetreden</h1> </div> ); } } export default MainView;
这个方案完全符合React和Redux的使用规范,而且定时器的清理也能避免内存泄漏,非常适合你的场景。
方案二:直接访问Redux Store实例
如果你的Redux Store是单独创建并导出的(比如在store.js里),那你可以在普通函数里直接导入Store实例,用store.getState()获取状态,用store.dispatch()触发更新,这样就不需要依赖Redux Hooks了。
步骤如下:
- 确保你的Store文件(比如
store.js)导出了store实例:
import { configureStore } from '@reduxjs/toolkit'; import rootReducer from './reducers'; export const store = configureStore({ reducer: rootReducer, });
- 修改你的
CheckIfChanged函数:
import { store } from '../path/to/store'; import { updateGameState } from '../path/to/your/actions'; async function getGamestate() { const gameStates = await fetch('http://localhost:3000/game-state').then(response => response.json()); return { data: gameStates }; } export async function CheckIfChanged(){ const serverGameState = await getGamestate(); const clientGameState = store.getState().gameState; // 同样注意深比较的问题 if (JSON.stringify(serverGameState.data) !== JSON.stringify(clientGameState)) { console.log(serverGameState); store.dispatch(updateGameState(serverGameState.data)); } else { console.log("still the same") } }
然后在MainView里用useEffect定时调用这个函数就行:
useEffect(() => { CheckIfChanged(); const intervalId = setInterval(CheckIfChanged, 5000); return () => clearInterval(intervalId); }, []);
不过这个方案要注意:如果你用的是服务端渲染(SSR),直接访问store实例可能会有状态污染的问题;而且这种方式脱离了React的生命周期,不如第一种方案贴合React的生态。
小提醒
不管用哪种方案,状态对比一定要用深比较!因为如果gameState是对象/数组,直接用!==只会比较引用地址,哪怕内容一样也会被判断为不同,这会导致不必要的更新。你可以用JSON.stringify(简单场景),或者引入lodash.isEqual(复杂场景更可靠)。
内容的提问来源于stack exchange,提问作者Heis

