combineReducers
の使用
コアコンセプト
Redux アプリの最も一般的な状態形状は、各トップレベルキーにドメイン固有のデータの「スライス」を含むプレーンな JavaScript オブジェクトです。同様に、その状態形状のリデューサーロジックを記述する最も一般的なアプローチは、同じ (state, action)
シグネチャを持ち、特定の状態のスライスへのすべての更新を管理する責任がある「スライスリデューサー」関数を持つことです。複数のスライスリデューサーは同じアクションに応答でき、必要に応じて独自のスライスを独立して更新し、更新されたスライスは新しい状態オブジェクトに結合されます。
このパターンは非常に一般的であるため、Redux はその動作を実装するための combineReducers
ユーティリティを提供します。これは、スライスリデューサー関数でいっぱいのオブジェクトを受け取り、新しいリデューサー関数を返す高階リデューサーの例です。
combineReducers
を使用する際に認識しておくべき重要なアイデアがいくつかあります。
- まず何よりも、
combineReducers
は単に Redux リデューサーを記述する際の最も一般的なユースケースを簡略化するユーティリティ関数 です。 独自のアプリケーションで使用する必要はなく、すべての可能なシナリオを処理するわけでもありません。それを使用せずにリデューサーロジックを記述することは完全に可能であり、combineReducer
が処理しないケースに対してカスタムリデューサーロジックを記述する必要があるのは非常に一般的です。(例と提案については、combineReducers
を超えて を参照してください。) - Redux 自体は状態の構成方法について意見を持っていませんが、
combineReducers
はユーザーがよくあるエラーを回避するのに役立ついくつかのルールを適用します。(詳細については、combineReducers
を参照してください。) - よくある質問の1つは、アクションをディスパッチするときに Redux が「すべてのリデューサーを呼び出す」かどうかです。実際にはルートリデューサー関数が1つしかないため、デフォルトの答えは「いいえ、呼び出しません」です。ただし、
combineReducers
にはそのように動作する特定の動作があります。新しい状態ツリーを組み立てるために、combineReducers
は各スライスリデューサーを現在の状態のスライスと現在のアクションで呼び出し、スライスリデューサーは必要に応じて応答し、状態のスライスを更新する機会を与えます。したがって、その意味では、combineReducers
を使用すると「すべてのリデューサー」または少なくともそれをラップしているすべてのスライスリデューサーを呼び出します。 - ルートリデューサーを作成するだけでなく、リデューサー構造のすべてのレベルで使用できます。さまざまな場所に複数の結合されたリデューサーがあり、それらが組み合わされてルートリデューサーを作成するのは非常に一般的です。
状態形状の定義
ストアの状態の初期形状と内容を定義する方法は2つあります。まず、createStore
関数は2番目の引数として preloadedState
を受け取ることができます。これは主に、以前にブラウザの localStorage など、他の場所に永続化されていた状態でストアを初期化することを目的としています。もう1つの方法は、状態引数が undefined
のときに、ルートリデューサーが初期状態値を返すことです。これらの2つのアプローチについては、状態の初期化 で詳しく説明していますが、combineReducers
を使用する場合に注意すべき追加の懸念事項がいくつかあります。
combineReducers
はスライスリデューサー関数でいっぱいのオブジェクトを受け取り、同じキーを持つ対応する状態オブジェクトを出力する関数を作成します。つまり、createStore
にプリロードされた状態が提供されていない場合、入力スライスリデューサーオブジェクトのキーの名前が、出力状態オブジェクトのキーの名前を定義します。特にデフォルトのモジュールエクスポートやオブジェクトリテラルのショートハンドなどの機能を使用する場合、これらの名前の相関関係は必ずしも明らかではありません。
これが、combineReducers
でオブジェクトリテラルのショートハンドを使用すると、状態形状を定義できる方法の例です。
// reducers.js
export default theDefaultReducer = (state = 0, action) => state
export const firstNamedReducer = (state = 1, action) => state
export const secondNamedReducer = (state = 2, action) => state
// rootReducer.js
import { combineReducers, createStore } from 'redux'
import theDefaultReducer, {
firstNamedReducer,
secondNamedReducer
} from './reducers'
// Use object literal shorthand syntax to define the object shape
const rootReducer = combineReducers({
theDefaultReducer,
firstNamedReducer,
secondNamedReducer
})
const store = createStore(rootReducer)
console.log(store.getState())
// {theDefaultReducer : 0, firstNamedReducer : 1, secondNamedReducer : 2}
オブジェクトリテラルを定義するためにショートハンドを使用したため、結果の状態のキー名は、インポートからの変数名と同じであることに注意してください。これは常に望ましい動作であるとは限らず、最新の JS 構文にあまり詳しくない人にとっては混乱の原因になることがよくあります。
また、結果の名前は少し奇妙です。一般的に、状態キー名に「reducer」のような単語を実際に入れるのは良い習慣ではありません。キーは、保持しているデータのドメインまたはタイプを単純に反映する必要があります。つまり、出力状態オブジェクトのキーを定義するために、スライスリデューサーオブジェクトのキーの名前を明示的に指定するか、ショートハンドオブジェクトリテラル構文を使用するときにキーを設定するために、インポートされたスライスリデューサーの変数を慎重に名前変更する必要があります。
より良い使い方は次のようになる可能性があります。
import { combineReducers, createStore } from 'redux'
// Rename the default import to whatever name we want. We can also rename a named import.
import defaultState, {
firstNamedReducer,
secondNamedReducer as secondState
} from './reducers'
const rootReducer = combineReducers({
defaultState, // key name same as the carefully renamed default export
firstState: firstNamedReducer, // specific key name instead of the variable name
secondState // key name same as the carefully renamed named export
})
const reducerInitializedStore = createStore(rootReducer)
console.log(reducerInitializedStore.getState())
// {defaultState : 0, firstState : 1, secondState : 2}
この状態形状は、combineReducers
に渡したキーを設定するために注意を払ったため、関連するデータをより良く反映しています。