使用RTK Query+Entity Adapter时无法通过SelectAll获取数据
问题:筛选指定userId的帖子时无法通过选择器获取数据
我编写了如下PostsSlice.js代码,尝试筛选userId对应的帖子,在App组件中调用useGetPostsQuery({userId:1})后,数据已成功存入Posts状态,但无法通过useSelector(selectAllPosts)获取数据;而调用无参数的useGetPostsQuery()时,却能正常通过该选择器获取数据。
import { createSelector, createEntityAdapter } from "@reduxjs/toolkit"; import { apiSlice } from "../../features/api/apiSlice"; import * as moment from "moment"; const postsAdapter = createEntityAdapter({ }); const initialState = postsAdapter.getInitialState(); export const postApiSlice = apiSlice.injectEndpoints({ endpoints: (builder) => ({ getPosts: builder.query({ query: (params) => { return { url: "/posts", params: params }; }, transformResponse: (responseData) => { return postsAdapter.setAll(initialState, responseData); }, providesTags: (result, error, arg) => [ { type: "Post", id: "LIST" }, ...result.ids.map((id) => ({ type: "Post", id })), ], }), }), }); export const { useGetPostsQuery, } = postApiSlice; export const selectPostsResult = postApiSlice.endpoints.getPosts.select(); const selectPostsData = createSelector( selectPostsResult, (postsResult) => postsResult.data ); export const { selectAll: selectAllPosts, selectById: selectPostById, selectIds: selectPostIds, } = postsAdapter.getSelectors( (state) => selectPostsData(state) ?? initialState );
原因分析与解决方案
核心原因
你的selectPostsResult定义为postApiSlice.endpoints.getPosts.select(),这会默认匹配无参数的查询缓存条目。当你调用useGetPostsQuery({userId:1})时,RTK Query会创建一个带参数的独立缓存条目,此时selectPostsResult找不到对应的数据,返回的postsResult.data为undefined,最终选择器会 fallback 到initialState,所以拿不到筛选后的帖子数据。
修复步骤
1. 修改选择器支持参数匹配
将selectPostsResult改为可接收查询参数的函数,让它能定位到对应参数的缓存:
// 原代码 // export const selectPostsResult = postApiSlice.endpoints.getPosts.select(); // 修改后 export const selectPostsResult = (params) => postApiSlice.endpoints.getPosts.select(params);
2. 调整数据选择器适配参数
更新selectPostsData,让它能根据传入的参数获取对应缓存:
// 原代码 // const selectPostsData = createSelector( // selectPostsResult, // (postsResult) => postsResult.data // ); // 修改后 const selectPostsData = createSelector( [ (state, params) => selectPostsResult(params)(state), (state, params) => params // 仅作为依赖项,确保参数变化时重新计算 ], (postsResult) => postsResult.data );
3. 组件中使用选择器时传入参数
在组件调用useSelector时,需要传入对应的查询参数:
// 组件内代码 const posts = useSelector(state => selectAllPosts(state, { userId: 1 }));
可选优化:创建带参数的专用选择器
为了更优雅地使用,可以直接创建一个接收参数的选择器:
export const selectAllPostsByParams = (params) => createSelector( (state) => selectPostsResult(params)(state), (postsResult) => postsResult.data ?? initialState, (postsState) => postsAdapter.getSelectors().selectAll(postsState) ); // 组件内使用 const posts = useSelector(selectAllPostsByParams({ userId: 1 }));
额外小修复:transformResponse的状态处理
transformResponse中直接使用全局的initialState虽然不影响功能,但更规范的写法是创建新的初始状态(避免意外的引用共享):
// 原代码 // transformResponse: (responseData) => { // return postsAdapter.setAll(initialState, responseData); // }, // 修改后 transformResponse: (responseData) => { return postsAdapter.setAll(postsAdapter.getInitialState(), responseData); },
内容的提问来源于stack exchange,提问作者Mario84
相关产品推荐
相关产品推荐

