如何获取RTK Query缓存响应数据更新Slice状态
RTK Query 的matchFulfilled匹配器只会在真实网络请求成功返回时触发对应action。相同参数的请求命中缓存直接返回已有数据时,不会派发网络请求层面的fulfilled action,因此监听该action的逻辑不会执行。
按优先级从高到低推荐以下方案,优先选符合RTK Query设计逻辑的写法,避免冗余状态。
方案1:直接在endpoint定义时处理数据转换(最推荐)
如果你的自定义逻辑只是对接口返回值做格式转换,完全不需要额外维护独立slice存转换结果,直接在定义接口时配置transformResponse,转换逻辑会在数据写入RTK Query缓存前执行。不管是首次网络请求返回,还是后续命中缓存读数据,拿到的都是转换后的结果,逻辑只需要写一次。
export const api = createApi({ baseQuery: fetchBaseQuery({ baseUrl: '/api' }), endpoints: (builder) => ({ getTokens: builder.query({ query: () => '/tokens', // 这里写原先放在matcher里的转换逻辑 transformResponse: (rawResponse) => { return rawResponse.map(item => ({ ...item, displayName: `${item.symbol} - ${item.name}` })) } }) }) })
后续在组件中通过useGetTokensQuery()取到的data就是已经处理好的格式,不需要额外同步状态。
方案2:用记忆化选择器派生状态(无冗余存储)
如果你需要把转换后的数据和其他业务逻辑联动,也不需要单独存一份状态,直接基于RTK Query的缓存结果写派生选择器即可,选择器会自动跟随缓存数据更新,首次请求、缓存命中场景都能拿到最新值:
import { createSelector } from '@reduxjs/toolkit' import { api } from './api' // 取到getTokens接口的缓存结果 const selectGetTokensResult = api.endpoints.getTokens.select() // 派生转换后的业务数据 export const selectProcessedTokens = createSelector( [selectGetTokensResult], (tokenResult) => { if (!tokenResult?.data) return [] // 这里写自定义转换逻辑 return tokenResult.data.map(item => ({ ...item, displayName: `${item.symbol} - ${item.name}` })) } )
业务代码中直接通过useSelector(selectProcessedTokens)就能拿到处理后的数据,没有状态同步成本,也不会出现多份数据不一致的问题。
方案3:通过onQueryStarted生命周期同步独立slice(必须保留独立slice时使用)
如果业务上确实需要把转换后的数据存入自定义slice维护,可以在endpoint定义时使用onQueryStarted生命周期钩子,这个钩子不管是首次发起网络请求,还是命中缓存调用接口,都会触发,queryFulfilled会在数据可用时(网络返回/缓存命中)正确resolve拿到数据:
// 先在你的itemsSlice里补上更新状态的reducer const itemsSlice = createSlice({ name: 'items', initialState, reducers: { setTokens: (state, {payload}) => { // 这里存转换后的数据 state.tokenList = payload } }, // 原先监听fulfilled的extraReducers可以删掉了 }) // 在api的endpoint定义中加生命周期逻辑 export const api = createApi({ baseQuery: fetchBaseQuery({ baseUrl: '/api' }), endpoints: (builder) => ({ getTokens: builder.query({ query: () => '/tokens', async onQueryStarted(_, { dispatch, queryFulfilled }) { try { const { data } = await queryFulfilled // 执行自定义转换 const processedData = data.map(item => ({ ...item, displayName: `${item.symbol} - ${item.name}` })) // 派发action更新自定义slice dispatch(itemsSlice.actions.setTokens(processedData)) } catch (err) { // 按需处理请求错误 } } }) }) })
- 不要监听RTK Query的内部action实现逻辑,内部action命名可能随版本迭代变更,会导致代码兼容性问题
- 不要在组件中拿到query返回的data后就手动dispatch更新slice,多组件同时调用该接口时会触发重复更新,拉高维护成本
内容的提问来源于stack exchange,提问作者Osmanys Fuentes-Lombá

