You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于店铺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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.20 09:28:10