createSelector
概要
ReselectライブラリのcreateSelector
ユーティリティを、使いやすくするために再エクスポートしています。
createSelector
の使い方に関する詳細は、以下を参照してください。
- Reselect APIドキュメント
- React-Redux ドキュメント: Hooks API - メモ化セレクターの使用
- Idiomatic Redux: カプセル化とパフォーマンスのためのReselectセレクターの使用
- React/Redux リンク: リデューサーとセレクター
v0.7より前では、RTKはselectorator
からcreateSelector
を再エクスポートしており、文字列キーパスを入力セレクターとして使用できました。これは最終的に十分なメリットを提供せず、文字列キーパスがセレクターの静的型付けを困難にしたため、削除されました。
createDraftSafeSelector
一般的に、リデューサー内でセレクターを使用することはお勧めしません。
- セレクターは通常、Redux状態オブジェクト全体を引数として期待しますが、スライスリデューサーはRedux状態全体の特定の部分にのみアクセスできます。
- Reselectの
createSelector
は、入力が変更されたかどうかを判断するために参照比較に依存しており、Immer Proxyでラップされたドラフト値がセレクターに渡された場合、セレクターは同じ参照を見て、何も変更されていないと考える可能性があります。
ただし、一部のユーザーは、Immerで駆動されるリデューサー内で正しく動作するセレクターを作成する機能を要求しています。このユースケースの1つは、createEntityAdapter
を使用する際に、const orderedTodos = todosSelectors.selectAll(todosState)
のように、順序付けられたアイテムのセットを収集し、残りのリデューサーロジックでorderedTodos
を使用することなどが考えられます。
createSelector
の再エクスポートに加えて、RTKはcreateDraftSafeSelector
という名前のcreateSelector
のラップされたバージョンもエクスポートしています。これにより、Immerで駆動される可変ロジックを持つcreateReducer
およびcreateSlice
リデューサー内で安全に使用できるセレクターを作成できます。プレーンな状態値で使用する場合、セレクターは入力に基づいて通常どおりメモ化されます。ただし、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)
それを実行すると、メモ化されたセレクターが同じオブジェクトで実行されたため、unsafe1
とunsafe2
は同じ値になります。しかし、安全なセレクターはImmerドラフトオブジェクトで実行されたことを検出し、キャッシュされた値を返す代わりに現在の値を使用して再計算したため、safe2
は実際にはsafe1
とは異なります(2
の更新された値)。
createDraftSafeSelectorCreator
RTKは、createSelectorCreator
の「ドラフトセーフ」版であるcreateDraftSafeSelectorCreator
関数もエクスポートしています。
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以降では、state
の型を組み込むことができる、事前型付けされたcreateDraftSafeSelector
のバージョンを定義できます。これにより、これらの型を一度設定するだけで、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(
// 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),
)