搜索

首页  >  问答  >  正文

如何将 RTK 查询与选择器结合使用

我正在使用 Redux ToolkitRTK 查询 构建一个项目,并尝试从 API 获取一些条目。我正在使用 createEntityAdapter 的规范化数据方法,因为在某个组件中我需要将数据作为数组,所以我最终使用了选择器。现在我的问题是,由于我添加过滤器作为查询的参数,我的选择器停止工作。

我在这里研究了类似的问题,例如:“如何使用带参数的 RTK 查询选择器?”,但我太笨了,无法理解我应该修改的内容。我试图理解 RTK 查询文档,但我做不到。

从上面的问题中,我知道我的选择器还需要具有参数,以便准确地知道要选择什么,并且这不是推荐的模式,但我无法理解如何使其工作。

我的条目切片:

import { createSelector, createEntityAdapter } from '@reduxjs/toolkit'
import { apiSlice } from './apiSlice'

const entryAdapter = createEntityAdapter()

const initialState = entryAdapter.getInitialState({
  ids: [],
  entities: {},
})

export const entryApiSlice = apiSlice.injectEndpoints({
  endpoints: (builder) => ({
    initialState,
    getEntry: builder.query({
      query: (filters) => ({
        url: '/history',
        params: filters,
      }),
      transformResponse: (responseData) => {
        return entryAdapter.setAll(initialState, responseData)
      },
      providesTags: (result, error, arg) => [
        { type: 'Entry', id: 'LIST' },
        ...result.ids.map((id) => ({ type: 'Entry', id })),
      ],
    }),
    addEntry: builder.mutation({
      query: (data) => ({
        url: '/history/new',
        method: 'POST',
        body: data,
      }),
      invalidatesTags: [{ type: 'Entry', id: 'LIST' }],
    }),
    updateEntry: builder.mutation({
      query: (initialEntry) => ({
        url: `/history/${initialEntry.Id}`,
        method: 'PUT',
        body: {
          ...initialEntry,
          date: new Date().toISOString(),
        },
      }),
      invalidatesTags: (result, error, arg) => [{ type: 'Entry', id: arg.id }],
    }),
    deleteEntry: builder.mutation({
      query: ({ id }) => ({
        url: `/history/${id}`,
        method: 'DELETE',
        body: { id },
      }),
      invalidatesTags: (result, error, arg) => [{ type: 'Entry', id: arg.id }],
    }),
  }),
})

export const {
  useGetEntryQuery,
  useAddEntryMutation,
  useUpdateEntryMutation,
  useDeleteEntryMutation,
} = entryApiSlice

export const selectEntryResult = (state, params) =>
  entryApiSlice.endpoints.getEntry.select(params)(state).data

const entrySelectors = entryAdapter.getSelectors(
  (state) => selectEntryResult(state) ?? initialState
)
export const selectEntry = entrySelectors.selectAll

我在像这样的 Entries 组件中使用它

const {
    data: entriesData = [],
    refetch,
    isLoading,
    isSuccess,
    isError,
    error,
  } = useGetEntryQuery(filters)

  const entries = useSelector(selectEntry)

注意:如果我从获取查询中删除“过滤器”,一切都会像以前一样工作(当然,正如预期的那样)。

免责声明:我不知道我到底在做什么,我已经阅读了文档并且正在尝试弄清楚,所以非常感谢任何反馈。 谢谢!

P粉729198207P粉729198207331 天前674

全部回复(1)我来回复

  • P粉779565855

    P粉7795658552023-12-29 00:13:52

    是的,这是一个有点敏感的话题,因为 RTKQ 的文档倾向于显示最简单的示例,即根本不使用任何参数的查询。我自己也遇到过很多问题。

    无论如何,您已将 selectEntryResult 声明为两个参数的函数:state 和 params。然后,当您在其下方创建适配器选择器时,您仅使用一个参数调用它:状态。此外,当您在组件中使用最终选择器时,如下所示:

    const entries = useSelector(selectEntry);
    

    参数无处可寻,默认情况下它们未定义,无法找到与此类查询参数关联的任何数据。

    这里要理解的关键是,您实际上需要以某种方式通过选择器的每个级别(每个包装器)传递查询参数。

    这里的一种方法是通过选择器“转发”参数:

    export const selectEntryResult = createSelector([state => state, (_, params) => params], (state, params) =>
      entryApiSlice.endpoints.getEntry.select(params)(state)?.data ?? initialState)
    

    这里我们使用从RTK导出的createSelector函数。然后在你的组件中你会做这样的事情:

      const {...} = useGetEntryQuery(filters);
    
      const entries = useSelector(state => selectEntry(state, filters));
    

    这在使用实体适配器创建的 selectAll 选择器时有效,但在使用 selectById 时会导致问题,因为该选择器也是参数化的。简而言之,selectById 选择器在内部定义为使用您希望检索的实体 id 的第二个参数,而我展示的方法使用第二个参数来传递查询参数(您的过滤器中的过滤器)。案例)。

    据我所知,到目前为止还没有解决方案能够完美运行并涵盖以下所有内容:

    • 使用实体规范化
    • 使用选择器检索数据
    • 使用参数化查询

    另一种方法可能是创建一些选择器工厂,这些选择器工厂为查询参数的特定组合动态创建基本选择器。

    我曾经制作过这样一个包装器,可以在所有情况下使用。不幸的是,我无法共享它,因为它是一个私有包,但基本思想是改变参数,以便 selectByIdselectAll (以及所有其他选择器)都可以正常工作,通过将查询参数作为第三个参数传递给选择器,然后进一步重新包装每个实体适配器选择器:

    export const createBaseSelector = (endpoint) =>
      createSelector(
        [(state) => state, (_, other) => other, (_, _other, params) => params],
        (state, _other, params) => endpoint.select(params)(state)
      );
    
    const selectors = adapter.getSelectors(baseSelector);
    
    // And then rewrap selectAll and selectById from 'selectors' above
    

    我知道这听起来很复杂,我几乎没有让它工作,所以尽量避免朝这个方向走:)

    可以找到我一路上找到的一篇有用的文章这里,他们还描述了一些在组件级别创建选择器并记住它们的方法,但我还没有全部尝试过。看看吧,也许您会找到更简单的方法来解决您的特定问题。

    回复
    0
  • 取消回复