我正在使用 Redux Toolkit 和 RTK Query 构建一个项目,并且我正在尝试从 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)
注意:如果我从 get 查询中删除
filters
,一切都会像以前一样工作(当然如预期)。
免责声明:我不知道我到底在做什么,我已阅读文档并且正在尝试弄清楚,因此非常感谢任何反馈。
谢谢!
是的,这是一个有点敏感的话题,因为 RTKQ 的文档倾向于显示最简单的示例,即根本不使用任何参数的查询。我自己也遇到过很多问题。
无论如何,您已经将
selectEntryResult
声明为两个参数的函数:state 和 params。然后,当您在其下方创建适配器选择器时,您仅使用一个参数调用它:状态。此外,当您在组件中使用最终选择器时,如下所示:
const entries = useSelector(selectEntry);
参数无处可寻,导致默认情况下
undefined
,无法找到与此类查询参数关联的任何数据。
这里要理解的关键是,您需要以某种方式实际上通过选择器的每个级别(每个包装器)传递查询参数。
这里的一种方法是通过选择器“转发”参数:
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 的第二个参数,而我展示的方法使用第二个参数来传递查询参数(在您的情况下是过滤器)。
据我所知,到目前为止,还没有解决方案能够完美运行并涵盖以下所有内容:
另一种方法可能是创建一些选择器工厂,这些选择器工厂为查询参数的特定组合动态创建基本选择器。
我曾经制作过这样一个包装器,可以在所有情况下使用。不幸的是,我无法共享它,因为它是一个私有包,但基本思想是通过将查询参数作为第三个参数传递来改变参数,以便
selectById
和 selectAll
(以及所有其他选择器)都可以正常工作到选择器,然后进一步重新包装每个实体适配器选择器:
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
我知道这听起来很复杂,我几乎没有让它工作,所以尽量避免朝这个方向走:)
我一路上发现的一篇有用的文章可以在here找到,他们还描述了一些在组件级别创建选择器并记住它们的方法,但我还没有全部尝试过。看看吧,也许您会找到更简单的方法来解决您的具体问题。