RTK Query跨端点缓存行为疑问及代码问题求助
解决RTK Query缓存与请求异常的两个问题
问题1:切换数据集时pending状态仍显示旧数据
原因
你通过条件分支调用不同的useQuery hook,违反了React Hook的核心规则——必须在组件顶层无条件调用hook。这种写法会导致React无法正确跟踪hook的状态,RTK Query的缓存也不会被及时清理,新请求pending时依然会渲染旧查询的缓存数据。另外RTK Query默认的stale-while-revalidate机制也会保留旧数据,但核心问题还是hook的条件调用导致的异常。
解决方案
不要在条件中切换useQuery调用,而是在组件顶层同时调用两个查询的useQuery,再根据当前状态决定展示哪组数据。同时判断当前选中查询的加载状态,pending时不渲染任何数据。
问题2:不传不同参数时第二个请求一直pending
原因
同样是违反React Hook调用规则导致的异常:条件调用hook会让React无法正确维护hook的调用顺序,当两个useQuery使用相同参数时,组件渲染逻辑混乱,导致第二个请求无法正常完成,一直处于pending状态。另外你的两个endpoint的query函数完全忽略了传入的参数,属于无意义的传参。
解决方案
- 遵守Hook规则,在组件顶层同时调用两个
useQuery; - 将endpoint的参数类型改为
void,不需要传入任何参数,避免无意义的传参。
修改后的完整代码
import React, { useState } from 'react'; import { createRoot } from 'react-dom/client'; import { Provider } from 'react-redux'; import { createApi } from '@reduxjs/toolkit/query/react'; import { combineReducers, configureStore } from '@reduxjs/toolkit'; const baseQuery = async (arg: string) => { await new Promise(resolve => setTimeout(resolve, 5000)); return { data: arg === 'odd' ? [1, 2, 3] : [2, 4, 6]}; }; const api = createApi({ baseQuery, endpoints: (build) => ({ // 参数类型改为void,无需传入参数 fetchOdd: build.query<number[], void>({ query: () => 'odd', providesTags: ['odd'] }), fetchEven: build.query<number[], void>({ query: () => 'even', providesTags: ['even'] }) }) }); const store = configureStore({ reducer: { [api.reducerPath]: api.reducer }, middleware: (getDefault) => getDefault().concat(api.middleware) }); const App = () => { const [dataset, setDataset] = useState('odd'); // 顶层同时调用两个useQuery,符合Hook规则 const { data: oddData, isLoading: isOddLoading } = api.endpoints.fetchOdd.useQuery(); const { data: evenData, isLoading: isEvenLoading } = api.endpoints.fetchEven.useQuery(); // 根据当前数据集匹配对应的数据和加载状态 const currentData = dataset === 'odd' ? oddData : evenData; const currentIsLoading = dataset === 'odd' ? isOddLoading : isEvenLoading; return ( <div> <button onClick={() => setDataset(dataset === 'odd' ? 'even' : 'odd')}> {dataset} </button> {/* 加载状态时不渲染数据,实现pending时内容为空的需求 */} {!currentIsLoading && currentData?.map(d => <div key={d}>{d}</div>)} </div> ); } const container = document.getElementById('app-root')!; const root = createRoot(container); root.render(<Provider store={store}><App/></Provider>);
内容的提问来源于stack exchange,提问作者Martin McCann
相关产品推荐
相关产品推荐

