基于店铺ID获取数据时,RTK Query中EntityAdapter与选择器配置问题
解决RTK Query中传递参数给查询选择器及EntityAdapter整合问题
核心问题分析
RTK Query的endpoints.[name].select()方法需要传入对应的查询参数,因为每个不同的参数组合会生成独立的缓存条目。你之前的代码没有传递参数,导致无法定位到正确的缓存数据。同时,当前的EntityAdapter使用方式也需要调整,避免在transformResponse中直接操作state,而是通过选择器将缓存的评论数据转换为Entity结构。
分步解决方案
1. 修正查询选择器的参数传递
将selectReviewsResult改为接收查询参数的函数,这样可以根据传入的id和role获取对应的缓存结果:
// 接收id和role参数,返回对应查询的缓存选择器 export const selectReviewsResult = (params: { id: string; role: string }) => reviewsApiSlice.endpoints.getReviews.select(params);
2. 调整选择器以支持参数传入
使用createSelector的多参数特性,让选择器同时接收Redux state和查询参数,从而定位到正确的缓存数据并转换为EntityAdapter需要的结构:
// 创建带参数的记忆化选择器,将缓存的评论数组转换为Entity结构 const selectReviewsData = createSelector( [ (state: RootState) => state, (_, params: { id: string; role: string }) => params ], (state, params) => { const reviewsResult = selectReviewsResult(params)(state); if (!reviewsResult.data) return initialState; // 将评论数组转换为EntityAdapter所需的{ids: [], entities: {}}结构 return reviewsAdapter.setAll(initialState, reviewsResult.data); } );
3. 重构EntityAdapter选择器
由于现在选择器需要接收参数,我们需要将getSelectors生成的选择器包装为接受参数的函数:
// 包装EntityAdapter的选择器,让它们支持传入查询参数 export const selectAllReviews = (params: { id: string; role: string }) => (state: RootState) => reviewsAdapter.selectAll(selectReviewsData(state, params)); export const selectReviewsById = (params: { id: string; role: string }, reviewId: string) => (state: RootState) => reviewsAdapter.selectById(selectReviewsData(state, params), reviewId); export const selectReviewIds = (params: { id: string; role: string }) => (state: RootState) => reviewsAdapter.selectIds(selectReviewsData(state, params));
4. 修正transformResponse的冗余问题
你当前的代码中重复定义了transformResponse,需要删除其中一个,只保留转换_id为id的逻辑即可:
export const reviewsApiSlice = apiSlice.injectEndpoints({ endpoints: (builder) => ({ getReviews: builder.query<IReview[], { id: string; role: string }>({ query: ({ id, role }) => ({ url: '/reviews', params: { id, role }, validateStatus: (response: Response, result: { isError: boolean }) => { return response.status === 200 && !result.isError; }, // 保留这个转换逻辑,将后端返回的_id替换为id transformResponse: (responseData: { data: IReview[] }) => { return responseData.data.map(review => { const { _id, ...rest } = review as any; return { ...rest, id: _id }; }); }, transformErrorResponse: (response: { status: string | number }) => response.status, providesTags: (result) => { if (!result) return [{ type: 'Review', id: 'LIST' }]; return [ { type: 'Review', id: 'LIST' }, ...result.map(review => ({ type: 'Review', id: review.id })) ]; }, }), // 删除这里重复的transformResponse }), }), });
完整修正后的代码
import { createSelector, createEntityAdapter } from '@reduxjs/toolkit'; import { apiSlice } from '../api/apiSlice'; import { RootState } from '../store'; import { IReview } from '../../components/helpers/Interfaces'; const reviewsAdapter = createEntityAdapter<IReview>({}); const initialState = reviewsAdapter.getInitialState(); export const reviewsApiSlice = apiSlice.injectEndpoints({ endpoints: (builder) => ({ getReviews: builder.query<IReview[], { id: string; role: string }>({ query: ({ id, role }) => ({ url: '/reviews', params: { id, role }, validateStatus: (response: Response, result: { isError: boolean }) => { return response.status === 200 && !result.isError; }, transformResponse: (responseData: { data: IReview[] }) => { return responseData.data.map(review => { const { _id, ...rest } = review as any; return { ...rest, id: _id }; }); }, transformErrorResponse: (response: { status: string | number }) => response.status, providesTags: (result) => { if (!result) return [{ type: 'Review', id: 'LIST' }]; return [ { type: 'Review', id: 'LIST' }, ...result.map(review => ({ type: 'Review', id: review.id })) ]; }, }), }), }), }); export const { useGetReviewsQuery } = reviewsApiSlice; // 接收查询参数,返回对应缓存的选择器 export const selectReviewsResult = (params: { id: string; role: string }) => reviewsApiSlice.endpoints.getReviews.select(params); // 记忆化选择器:将缓存数据转换为Entity结构 const selectReviewsData = createSelector( [ (state: RootState) => state, (_, params: { id: string; role: string }) => params ], (state, params) => { const reviewsResult = selectReviewsResult(params)(state); return reviewsResult.data ? reviewsAdapter.setAll(initialState, reviewsResult.data) : initialState; } ); // 包装EntityAdapter选择器,支持传入查询参数 export const selectAllReviews = (params: { id: string; role: string }) => (state: RootState) => reviewsAdapter.selectAll(selectReviewsData(state, params)); export const selectReviewsById = (params: { id: string; role: string }, reviewId: string) => (state: RootState) => reviewsAdapter.selectById(selectReviewsData(state, params), reviewId); export const selectReviewIds = (params: { id: string; role: string }) => (state: RootState) => reviewsAdapter.selectIds(selectReviewsData(state, params));
使用示例
在组件或其他地方使用选择器时,需要传入对应的id和role参数:
// 获取某店铺的所有评论 const shopReviews = useSelector(selectAllReviews({ id: 'shop123', role: 'shop' })); // 获取某评论的详情 const specificReview = useSelector(selectReviewsById({ id: 'user456', role: 'user' }, 'review789'));
内容的提问来源于stack exchange,提问作者Vlada Stefanovic
相关产品推荐
相关产品推荐

