跳到主要内容

 

createSelector

概述

createSelectorReselect 库的实用工具,为了方便使用而重新导出。

关于如何使用 createSelector 的更多细节,请参见:

备注

在 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 还导出了一个名为 createDraftSafeSelectorcreateSelector 包装版本,允许你创建可以安全地在 createReducercreateSlice 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)

执行后,unsafe1unsafe2 将具有相同的值,因为记忆化的选择器在同一对象上执行 - 但 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

已知的限制

目前这种方法只在输入选择器作为单个数组提供时有效。

如果你将输入选择器作为单独的内联参数传递,结果函数的参数类型将不会被推断。作为解决方案,你可以

  1. 将你的输入选择器包装在一个数组中
  2. 你可以注解结果函数的参数类型:
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),
)