メインコンテンツにスキップ

基本的な Reducer の構造と状態シェイプ

基本的な Reducer の構造

まず第一に、アプリケーション全体には実際には**1つの Reducer 関数**しかないことを理解することが重要です。それは、`createStore` の最初の引数として渡した関数です。この1つの Reducer 関数は、最終的にいくつかのことを行う必要があります。

  • Reducer が最初に呼び出されたとき、`state` の値は `undefined` になります。Reducer は、受信したアクションを処理する前に、デフォルトの state 値を提供することで、このケースを処理する必要があります。
  • 以前の state とディスパッチされたアクションを見て、どのような種類の作業を行う必要があるかを判断する必要があります。
  • 実際に変更が必要な場合は、更新されたデータを含む新しいオブジェクトと配列を作成し、それらを返す必要があります。
  • 変更が必要ない場合は、既存の state をそのまま返す必要があります。

Reducer ロジックを記述する最も簡単な方法は、次のようにすべてを1つの関数宣言にまとめることです。

function counter(state, action) {
if (typeof state === 'undefined') {
state = 0 // If state is undefined, initialize it with a default value
}

if (action.type === 'INCREMENT') {
return state + 1
} else if (action.type === 'DECREMENT') {
return state - 1
} else {
return state // In case an action is passed in we don't understand
}
}

この単純な関数がすべての基本的な要件を満たしていることに注意してください。既存のデータがない場合はデフォルト値を返し、ストアを初期化します。アクションのタイプに基づいてどのような種類の更新を行う必要があるかを判断し、新しい値を返します。そして、作業を行う必要がない場合は、以前の state を返します。

この Reducer に対していくつかの簡単な調整を行うことができます。まず、繰り返される `if`/`else` 文はすぐに面倒になるため、代わりに `switch` 文を使用するのが一般的です。次に、デフォルトのパラメーター値を使用して、初期の「既存データなし」ケースを処理できます。これらの変更を加えると、Reducer は次のようになります。

function counter(state = 0, action) {
switch (action.type) {
case 'INCREMENT':
return state + 1
case 'DECREMENT':
return state - 1
default:
return state
}
}

これは、典型的な Redux Reducer 関数が使用する基本的な構造です。

基本的な状態シェイプ

Redux は、管理する必要があるデータの観点からアプリケーションを考えることを推奨しています。任意の時点におけるデータはアプリケーションの「*状態*」であり、その状態の構造と構成は通常「*シェイプ*」と呼ばれます。状態のシェイプは、Reducer ロジックの構造化方法に大きな役割を果たします。

Redux の状態は、通常、プレーンな Javascript オブジェクトを状態ツリーの最上位に持ちます。(代わりに、単一の数字、配列、または特殊なデータ構造など、別のタイプのデータを持つことも確かに可能ですが、ほとんどのライブラリは最上位の値がプレーンなオブジェクトであると想定しています。)その最上位オブジェクト内のデータを整理する最も一般的な方法は、データをサブツリーにさらに分割することです。各最上位キーは、関連データの「ドメイン」または「スライス」を表します。たとえば、基本的な Todo アプリの状態は次のようになります。

{
visibilityFilter: 'SHOW_ALL',
todos: [
{
text: 'Consider using Redux',
completed: true,
},
{
text: 'Keep all state in a single tree',
completed: false
}
]
}

この例では、`todos` と `visibilityFilter` はどちらも状態の最上位キーであり、それぞれ特定の概念のデータの「スライス」を表します。

ほとんどのアプリケーションは複数のタイプのデータを扱いますが、大きく3つのカテゴリに分類できます。

  • *ドメインデータ*:アプリケーションが表示、使用、または変更する必要があるデータ(「サーバーから取得されたすべての Todo」など)
  • *アプリの状態*:アプリケーションの動作に固有のデータ(「Todo #5 が現在選択されている」または「Todo を取得するためのリクエストが進行中である」など)
  • *UI の状態*:UI が現在どのように表示されているかを表すデータ(「EditTodo モーダルダイアログが現在開いている」など)

ストアはアプリケーションの中核を表すため、**UI コンポーネントツリーではなく、ドメインデータとアプリの状態の観点から状態シェイプを定義する必要があります。** たとえば、`state.leftPane.todoList.todos` のシェイプは悪い考えです。「todos」という概念は、UI の一部だけでなく、アプリケーション全体の中心となるためです。`todos` スライスは、代わりに状態ツリーの最上位にある必要があります。

UI ツリーと状態シェイプの間に 1 対 1 の対応関係があることは*めったに*ありません。その例外は、Redux ストアで UI データのさまざまな側面も明示的に追跡している場合ですが、それでも UI データのシェイプとドメインデータのシェイプは異なる可能性があります。

典型的なアプリの状態シェイプは、おおよそ次のようになります。

{
domainData1 : {},
domainData2 : {},
appState1 : {},
appState2 : {},
ui : {
uiState1 : {},
uiState2 : {},
}
}