createSelector
概述
createSelector
是 Reselect 库的实用工具,为了方便使用而重新导出。
关于如何使用 createSelector
的更多细节,请参见:
- Reselect API 文档
- React-Redux 文档:Hooks API - 使用记忆选择器
- 惯用 Redux:使用 Reselect 选择器进行封装和性能优化
- React/Redux 链接:Reducers 和 Selectors
在 v0.7 之前,RTK 从 selectorator
重新导出了 createSelector
,这允许使用字符串键路径作为输入选择器。这被移除了,因为它最终没有提供足够的好处,而且字符串键路径使得选择器的静态类型化变得困难。
createDraftSafeSelector
通常,我们不建议在 reducers 中使用选择器:
- 选择器通常期望整个 Redux 状态对象作为参数,而切片 reducers 只能访问整个 Redux 状态的特定子集
- Reselect 的
createSelector
依赖于引用比较来确定输入是否已更改,如果将 Immer Proxy 包装的草稿值传递给选择器,选择器可能会看到相同的引用并认为没有任何改变。
然而,一些用户请求创建能在 Immer-powered reducers 中正确工作的选择器。这样的一个用例可能是当使用 createEntityAdapter
收集有序的项目集时,例如 const orderedTodos = todosSelectors.selectAll(todosState)
,然后在剩余的 reducer 逻辑中使用 orderedTodos
。
除了重新导出 createSelector
,RTK 还导出了一个名为 createDraftSafeSelector
的 createSelector
包装版本,允许你创建可以安全地在 createReducer
和 createSlice
reducers 中使用的选择器,这些 reducer 使用 Immer-powered 可变逻辑。当与普通状态值一起使用时,选择器将根据输入正常地进行记忆化。但是,当与 Immer 草稿值一起使用时,选择器将倾向于重新计算结果,以确保安全。
所有由 entityAdapter.getSelectors
创建的选择器默认都是 "draft safe" 选择器。
示例:
const selectSelf = (state: State) => state
const unsafeSelector = createSelector(selectSelf, (state) => state.value)
const draftSafeSelector = createDraftSafeSelector(
selectSelf,
(state) => state.value,
)
// 在你的 reducer 中:
state.value = 1
const unsafe1 = unsafeSelector(state)
const safe1 = draftSafeSelector(state)
state.value = 2
const unsafe2 = unsafeSelector(state)
const safe2 = draftSafeSelector(state)
执行后,unsafe1
和 unsafe2
将具有相同的值,因为记忆化的选择器在同一对象上执行 - 但 safe2
实际上会与 safe1
不同(更新值为 2
),因为安全选择器检测到它在 Immer 草稿对象上执行并使用当前值重新计算,而不是返回缓存值。
createDraftSafeSelectorCreator
RTK 还导出了一个 createDraftSafeSelectorCreator
函数,这是 createSelectorCreator
的 "draft safe" 等价物。
import {
createDraftSafeSelectorCreator,
weakMapMemoize,
} from '@reduxjs/toolkit'
const createWeakMapDraftSafeSelector =
createDraftSafeSelectorCreator(weakMapMemoize)
const selectSelf = (state: State) => state
const draftSafeSelector = createWeakMapDraftSafeSelector(
selectSelf,
(state) => state.value,
)
定义预类型的 createDraftSelector
从 RTK 2.1 开始,你可以定义一个 "预类型" 的 createDraftSafeSelector
,可以内置 state
的类型。这让你可以一次设置这些类型,所以你不必每次调用 createDraftSafeSelector
时都重复它们。
const createTypedDraftSafeSelector =
createDraftSafeSelector.withTypes<RootState>()
导入并使用预类型的 createTypedDraftSafeSelector
函数,它将自动知道 state
参数的类型是 RootState
。
目前这种方法只在输入选择器作为单个数组提供时有效。
如果你将输入选择器作为单独的内联参数传递,结果函数的参数类型将不会被推断。作为解决方案,你可以
- 将你的输入选择器包装在一个数组中
- 你可以注解结果函数的参数类型:
import { createSelector } from 'reselect'
interface Todo {
id: number
completed: boolean
}
interface Alert {
id: number
read: boolean
}
export interface RootState {
todos: Todo[]
alerts: Alert[]
}
export const createTypedDraftSafeSelector =
createDraftSafeSelector.withTypes<RootState>()
const selectTodoIds = createTypedDraftSafeSelector(
// `state` 的类型设置为 `RootState`,无需手动设置类型
(state) => state.todos,
// ❌ 已知的限制:在这种情况下,参数类型不会被推断
// 所以你将不得不手动注解它们。
(todos: Todo[]) => todos.map(({ id }) => id),
)