跳至主要內容

createSelector

總覽

createSelector 工具程式來自 Reselect 函式庫,重新匯出以方便使用。

有關使用 createSelector 的更多詳細資訊,請參閱

注意

在 v0.7 之前,RTK 從 selectorator 重新導出 createSelector,它允許使用字串鍵路徑作為輸入選擇器。這已被移除,因為它最終沒有提供足夠的好處,而且字串鍵路徑使得選擇器的靜態類型化變得困難。

createDraftSafeSelector

一般來說,我們建議不要在 reducer 內使用選擇器

  • 選擇器通常預期整個 Redux 狀態物件作為引數,而區塊 reducer 只能存取整個 Redux 狀態的特定子集
  • Reselect 的 createSelector 依賴於參考比較來判斷輸入是否已變更,如果 Immer Proxy 包裝的草稿值傳遞到選擇器中,選擇器可能會看到相同的參考並認為沒有任何變更。

然而,一些使用者已要求建立可以在 Immer 驅動的 reducer 內正確運作的選擇器。這樣的一個用例可能是使用 createEntityAdapter 時收集一組已排序的項目,例如 const orderedTodos = todosSelectors.selectAll(todosState),然後在 reducer 邏輯的其餘部分使用 orderedTodos

除了重新導出 createSelector 之外,RTK 還導出 createSelector 的包裝版本,稱為 createDraftSafeSelector,它允許你建立可以在 createReducercreateSlice reducer 內安全使用的選擇器,這些 reducer 具有 Immer 驅動的可變邏輯。當與純粹的狀態值一起使用時,選擇器仍會根據輸入正常記憶化。但是,當與 Immer 草稿值一起使用時,選擇器會出於安全考量而重新計算結果。

entityAdapter.getSelectors 建立的所有選擇器預設都是「草稿安全」選擇器。

範例

const selectSelf = (state: State) => state
const unsafeSelector = createSelector(selectSelf, (state) => state.value)
const draftSafeSelector = createDraftSafeSelector(
selectSelf,
(state) => state.value,
)

// in your 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 的「草稿安全」等效函式。

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(
// Type of `state` is set to `RootState`, no need to manually set the type
(state) => state.todos,
// ❌ Known limitation: Parameter types are not inferred in this scenario
// so you will have to manually annotate them.
(todos: Todo[]) => todos.map(({ id }) => id),
)