メインコンテンツへスキップ
非公式ベータ版翻訳

このページは PageTurner AI で翻訳されました(ベータ版)。プロジェクト公式の承認はありません。 エラーを見つけましたか? 問題を報告 →

combineReducersの使用

基本概念

Reduxアプリケーションにおける最も一般的なstateの構造は、トップレベルの各キーにドメイン固有のデータ「スライス」を含むプレーンなJavaScriptオブジェクトです。同様に、このstate構造に対応するリデューサロジックを記述する最も一般的なアプローチは、「スライスリデューサ」関数を使用することです。各スライスリデューサは同じ(state, action)シグネチャを持ち、特定のstateスライスの更新をすべて管理します。複数のスライスリデューサが同じアクションに応答し、必要に応じてそれぞれのスライスを独立して更新でき、更新されたスライスは新しいstateオブジェクトに組み合わされます。

このパターンは非常に一般的なため、Reduxはこの動作を実装するcombineReducersユーティリティを提供します。これは高階リデューサの一例であり、スライスリデューサ関数のオブジェクトを受け取り、新しいリデューサ関数を返します。

combineReducersを使用する際に認識すべき重要なポイントがいくつかあります:

  • 何よりもまず、combineReducersReduxリデューサを記述する際の最も一般的なユースケースを簡素化するためのユーティリティ関数に過ぎません。アプリケーションで使用することは必須ではなく、すべてのシナリオを処理するわけでもありません。これを使用せずにリデューサロジックを記述することは完全に可能であり、combineReducerが扱えないケースではカスタムロジックを書く必要がよくあります(例と提案についてはBeyond combineReducersを参照)。

  • Redux自体はstateの編成方法について意見を持ちませんが、combineReducersは一般的なエラーを避けるためにいくつかのルールを強制します(詳細はcombineReducers参照)。

  • よくある質問として、アクションをディスパッチする際にReduxが「すべてのリデューサを呼び出すか」というものがあります。実際にはルートリデューサ関数は1つしかないため、デフォルトの答えは「いいえ」です。しかし、combineReducersには実際にそのように動作する特定の挙動があります。新しいstateツリーを組み立てるために、combineReducersは各スライスリデューサを現在のstateスライスとアクションで呼び出し、必要に応じてスライスを更新する機会を与えます。この意味では、combineReducersを使用すると「すべてのリデューサを呼び出す」、あるいは少なくともラップしているすべてのスライスリデューサを呼び出すと言えます。

  • ルートリデューサの作成だけでなく、リデューサ構造のあらゆるレベルで使用できます。様々な場所で複数の結合されたリデューサを持ち、それらを合成してルートリデューサを作成することは非常に一般的です。

state構造の定義

ストアのstateの初期構造と内容を定義する方法は2つあります。1つ目は、createStore関数が第2引数としてpreloadedStateを取る方法です。これは主に、ブラウザのlocalStorageなど、以前に保存されたstateでストアを初期化するために使用されます。もう1つは、state引数がundefinedの場合にルートリデューサが初期state値を返す方法です。これらのアプローチについてはstateの初期化で詳しく説明していますが、combineReducersを使用する際には追加の注意点があります。

combineReducersはスライスリデューサ関数のオブジェクトを受け取り、同じキーを持つ対応するstateオブジェクトを出力する関数を作成します。これは、createStoreにpreloaded stateが提供されない場合、入力スライスリデューサオブジェクトのキー名が出力stateオブジェクトのキー名を定義することを意味します。これらの名前の対応関係は、デフォルトモジュールエクスポートやオブジェクトリテラルの省略記法などの機能を使用する場合、必ずしも明確ではありません。

以下は、combineReducersでオブジェクトリテラルの省略記法を使用してstate構造を定義する例です:

// 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}

オブジェクトリテラル定義の省略記法を使用したため、結果のstateのキー名はインポートからの変数名と同じになっていることに注意してください。これは必ずしも望ましい動作とは限らず、現代の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に渡すキーを注意深く設定したため、関連するデータをより適切に反映しています。