RTK Query:如何实现列表与单条教师数据的缓存共享?
问题
我有两个API端点:一个用于获取包含完整关联数据的教师列表,另一个用于获取单个教师数据。我希望这两个端点能够共享缓存数据。
需求背景是我有两个页面:一个展示教师列表,另一个展示指定教师的详情。我想复用列表缓存中已有的教师数据,若缓存中不存在则按需获取单条数据。
以下是两个端点的代码实现:
getTeachers: builder.query({ query: ({ page, orderBy, orderDirection }) => buildQueryWithParams('teachers', { limit: bigPaginationLimit, offset: (page - 1) * bigPaginationLimit, orderBy, orderDirection }), providesTags: (res) => res.list ? [ res.list.map((item) => ({ type: 'teacher', id: item.id })) ] : [{ type: 'teacher', id: 'list' }] getTeacher: builder.query({ query: (id) => `/teachers/${id}`, providesTags: (res, error, id) => [{ type: 'teacher', id }] })
我原本以为设置相同的tags就能实现缓存共享,但后来发现RTK Query并不通过tags管理缓存。
请问是否有办法让这两个端点共享缓存?如果没有,如何实现优先复用列表缓存数据,否则获取单条教师数据?
回答
首先明确:RTK Query的缓存是基于**查询键(由端点名称+查询参数生成)**来存储的,tags的作用是触发缓存失效,不是用来跨端点共享缓存的。所以直接靠tags没法实现缓存共享,但可以通过两种方式实现你的需求:
方式一:组件层优先读取列表缓存
在教师详情页组件中,先调用列表查询并通过selectFromResult提取目标教师的数据,当提取到数据时,跳过单条查询请求:
import { useGetTeachersQuery, useGetTeacherQuery } from './your-api-slice'; function TeacherDetail({ teacherId }) { // 先从列表缓存中查找目标教师(这里假设列表用的是第一页参数,实际要和列表页一致) const { data: teacherFromList } = useGetTeachersQuery( { page: 1, orderBy: 'id', orderDirection: 'asc' }, { selectFromResult: (result) => ({ ...result, data: result.data?.list?.find(t => t.id === teacherId) }) } ); // 当列表中已有数据时,跳过单条查询 const { data: teacherFromSingle } = useGetTeacherQuery(teacherId, { skip: !!teacherFromList }); // 优先使用列表缓存的数据 const teacherData = teacherFromList || teacherFromSingle; if (!teacherData) return <div>加载中...</div>; return ( <div> <h1>{teacherData.name}</h1> {/* 其他详情内容 */} </div> ); }
这种方式简单直接,适合列表数据包含教师完整详情的场景。如果列表是分页的,你可能需要处理多页缓存的情况(比如遍历所有getTeachers的缓存条目)。
方式二:在查询端点中手动读取缓存
通过自定义queryFn,让getTeacher端点先检查列表缓存,找不到数据再发起请求:
getTeacher: builder.query({ queryFn: async (id, { getState, extra, endpointName, queryCache }) => { // 遍历所有getTeachers的缓存条目,查找目标教师 const teachersQueries = queryCache.getQueriesData({ queryKey: ['getTeachers'] }); for (const [_, cacheData] of teachersQueries) { if (cacheData?.data?.list) { const foundTeacher = cacheData.data.list.find(t => t.id === id); if (foundTeacher) { return { data: foundTeacher }; } } } // 缓存中没有,发起API请求 const response = await extra.fetchBaseQuery(`/teachers/${id}`); return response; }, providesTags: (res, error, id) => [{ type: 'teacher', id }] })
这种方式把缓存复用逻辑封装在API层,组件中只需调用useGetTeacherQuery即可,无需额外处理。但要注意,queryCache的API需要你熟悉RTK Query的内部缓存结构。
补充说明
如果想保证列表和单条数据的一致性(比如更新单条教师数据后,列表缓存也同步更新),可以继续使用providesTags和invalidatesTags:当单条数据更新时,失效对应的teacher标签,触发列表查询重新获取数据,保证两边数据一致。
内容的提问来源于stack exchange,提问作者biitse

