React中RTK Query结合Socket.io更新单对象数据异常问题
RTK Query订阅更新单对象时数据为空的问题解决
问题背景
用React、RTK Query和Socket.io开发多人游戏,参考RTK Query文档的聊天订阅案例,把原案例的数组追加逻辑改成更新单对象后,数据一直是空对象{};但用数组方案(把对象存在draft[0]里)能正常运行。两者差异点:
- 泛型从
<ClientGame[]>改为<ClientGame> queryFn返回数据从[]改为{} as ClientGameupdateCachedData里从draft[0] = newGameState改成draft = newGameState
VS Code提示draft未被使用。
问题原因
RTK Query的updateCachedData依赖Immer处理不可变更新,Immer的核心逻辑是修改draft代理对象的属性来生成新状态,而非直接给draft变量重新赋值。直接写draft = newGameState只是改变了局部变量的引用,根本没触碰到Immer追踪的代理对象,所以缓存不会更新,最终数据还是初始的空对象。
而数组方案里的draft[0] = newGameState是修改数组draft的元素,属于Immer允许的操作,因此能正常更新缓存。
解决方案
要维护单对象缓存,不能直接给draft赋值,可用以下两种方式修改:
方案1:用Object.assign合并属性
把新对象的属性合并到draft中,适合浅结构的对象:
const gameApi = createApi({ reducerPath: 'gameApi', baseQuery: fakeBaseQuery(), endpoints: (builder) => ({ subscribeToGameState: builder.subscription<ClientGame>({ queryFn: () => ({ data: {} as ClientGame }), async subscribe(listener, api, extraOptions) { const socket = io('/game'); socket.on('gameState', (newGameState) => { api.dispatch( gameApi.util.updateCachedData( 'subscribeToGameState', (draft) => { // 合并新状态到draft,触发Immer更新 Object.assign(draft, newGameState); } ) ); }); return () => socket.close(); }, }), }), });
方案2:直接返回新状态对象
Immer支持在更新函数里直接返回新的状态对象,替换原缓存:
const gameApi = createApi({ reducerPath: 'gameApi', baseQuery: fakeBaseQuery(), endpoints: (builder) => ({ subscribeToGameState: builder.subscription<ClientGame>({ queryFn: () => ({ data: {} as ClientGame }), async subscribe(listener, api, extraOptions) { const socket = io('/game'); socket.on('gameState', (newGameState) => { api.dispatch( gameApi.util.updateCachedData( 'subscribeToGameState', () => { // 直接返回新状态,Immer会处理替换逻辑 return newGameState; } ) ); }); return () => socket.close(); }, }), }), });
补充说明
如果ClientGame是嵌套较深的对象,Object.assign只能浅合并,此时可以用Immer的嵌套修改(比如draft.someNestedProp = newNestedValue),或者引入深度合并工具(比如lodash的merge)来处理。
内容的提问来源于stack exchange,提问作者Marci
相关产品推荐
相关产品推荐

