Redux 基礎 Part 2: 主要概念とデータフロー
このページは PageTurner AI で翻訳されました(ベータ版)。プロジェクト公式の承認はありません。 エラーを見つけましたか? 問題を報告 →
- Redux 使用における主要な用語と概念
- Redux アプリケーションでのデータフロー
はじめに
Part 1: Redux 概要では、Redux とは何か、なぜ使用するのかについて説明し、Redux コアと併用される一般的なライブラリを紹介しました。また動作する Redux アプリの小さな例と、アプリを構成する要素についても確認しました。最後に Redux で使用されるいくつかの用語と概念について簡単に触れました。
このセクションでは、これらの用語と概念をより詳細に解説し、Redux アプリケーションにおけるデータフローについて深く掘り下げます。
このページは PageTurner AI で翻訳されました(ベータ版)。プロジェクト公式の承認はありません。 エラーを見つけましたか? 問題を報告 →
このチュートリアルでは、Reduxの基本原則と概念を説明するために、あえて旧来のスタイルのReduxロジックパターンを使用しています。これらは、現代的なReduxアプリ開発の正しいアプローチとして推奨しているRedux Toolkitを使った「モダンRedux」パターンに比べてコード量が多くなります。このチュートリアルは_プロダクション環境で使用することを想定したものではありません_。
「モダンRedux」をRedux Toolkitで実践する方法については、以下のページを参照してください:
- 完全版「Redux Essentials」チュートリアル: 現実世界のアプリケーションでRedux Toolkitを使った「Reduxの正しい使い方」を解説しています。すべてのRedux学習者は「Essentials」チュートリアルの読了を推奨します!
- Redux Fundamentals パート8: Redux ToolkitによるモダンRedux: 前半セクションのローレベルな例をモダンなRedux Toolkitの実装に変換する方法を解説
背景となる概念
実際のコードに入る前に、Reduxを使用する際に必要な主要な用語と概念について説明します。
状態管理
シンプルなReactカウンターコンポーネントから見ていきましょう。コンポーネント状態で数値を管理し、ボタンクリックで増加します:
function Counter() {
// State: a counter value
const [counter, setCounter] = useState(0)
// Action: code that causes an update to the state when something happens
const increment = () => {
setCounter(prevCounter => prevCounter + 1)
}
// View: the UI definition
return (
<div>
Value: {counter} <button onClick={increment}>Increment</button>
</div>
)
}
この自律的なアプリは以下の要素で構成されます:
-
状態(State): アプリを駆動する信頼できる情報源
-
ビュー(View): 現在の状態に基づくUIの宣言的記述
-
アクション(Actions): ユーザー操作に基づいて発生し、状態更新をトリガーするイベント
これは**「単方向データフロー」**の小規模な例です:
-
状態(State)は特定の時点におけるアプリの状態を記述する
-
UIはその状態に基づいてレンダリングされる
-
何かが発生すると(ユーザーのボタンクリックなど)、発生した内容に基づいて状態が更新される
-
UIは新しい状態に基づいて再レンダリングされる

しかし、同じ状態を共有して使用する必要がある複数のコンポーネントが存在する場合、特にそれらがアプリケーションの異なる部分に配置されている場合、このシンプルさは崩れる可能性があります。これは親コンポーネントへの「状態のリフトアップ」で解決できることもありますが、常に有効とは限りません。
この問題を解決する一つの方法は、共有状態をコンポーネントから抽出し、コンポーネントツリー外の中央集権的な場所に置くことです。これによりコンポーネントツリーは大きな「ビュー」となり、ツリー内のどこに位置していても、どのコンポーネントも状態にアクセスしたりアクションをトリガーしたりできるようになります。
状態管理に関わる概念を定義して分離し、ビューと状態の間の独立性を維持するルールを適用することで、コードの構造化と保守性を高めることができます。
これがReduxの基本概念です。アプリケーションのグローバル状態を一元管理する場所と、状態更新時にコードを予測可能にする特定のパターンを提供します。
不変性
「Mutable(可変)」とは「変更可能」を意味します。「Immutable(不変)」なものは決して変更できません。
JavaScriptのオブジェクトと配列はデフォルトで可変です。オブジェクトを作成するとそのフィールド内容を変更でき、配列を作成するとその内容も同様に変更できます:
const obj = { a: 1, b: 2 }
// still the same object outside, but the contents have changed
obj.b = 3
const arr = ['a', 'b']
// In the same way, we can change the contents of this array
arr.push('c')
arr[1] = 'd'
これはオブジェクトや配列の_ミューテート(変更)_と呼ばれます。メモリ内の同じオブジェクト/配列参照ですが、内部の内容が変化しています。
値を不変的に更新するには、コードが既存のオブジェクト/配列の_コピー_を作成し、そのコピーを変更する必要があります。
これはJavaScriptの配列/オブジェクトスプレッド演算子や、元の配列を変更せずに新しいコピーを返す配列メソッドを使って手動で実現できます:
const obj = {
a: {
// To safely update obj.a.c, we have to copy each piece
c: 3
},
b: 2
}
const obj2 = {
// copy obj
...obj,
// overwrite a
a: {
// copy obj.a
...obj.a,
// overwrite c
c: 42
}
}
const arr = ['a', 'b']
// Create a new copy of arr, with "c" appended to the end
const arr2 = arr.concat('c')
// or, we can make a copy of the original array:
const arr3 = arr.slice()
// and mutate the copy:
arr3.push('c')
Redux ではすべての状態更新は不変的に行われることを前提としています。これが重要な理由と場所、そして不変的更新ロジックを簡単に書く方法については後ほど説明します。
JavaScriptにおける不変性の動作についての詳細は以下を参照してください:
Redux 用語集
続ける前に理解すべき重要な Redux 用語があります:
アクション(Actions)
アクションはtypeフィールドを持つプレーンなJavaScriptオブジェクトです。アクションはアプリケーションで発生した事象を記述するイベントと考えることができます。
typeフィールドは"todos/todoAdded"のように説明的な名前を持つ文字列であるべきです。通常、タイプ文字列は"domain/eventName"形式で記述します。前半はアクションが属する機能やカテゴリ、後半は発生した具体的な事象を示します。
アクションオブジェクトには発生した内容に関する追加情報を含む他のフィールドを持たせられます。慣例的にこの情報はpayloadフィールドに配置します。
典型的なアクションオブジェクトは次のようになります:
const addTodoAction = {
type: 'todos/todoAdded',
payload: 'Buy milk'
}
リデューサー(Reducers)
リデューサーは、現在のstateとactionオブジェクトを受け取り、必要に応じて状態を更新する方法を決定し、新しい状態を返す関数です: (state, action) => newState。リデューサーは、受け取ったアクション(イベント)のタイプに基づいてイベントを処理するイベントリスナーと考えることができます。
「リデューサー」関数の名称は、Array.reduce()メソッドに渡すコールバック関数と類似していることに由来します。
Reducerは常に以下のルールに従わなければなりません:
-
stateとaction引数に基づいてのみ新しい状態値を計算する -
既存の
stateを変更してはならない。代わりに、既存のstateをコピーしコピーした値を変更するという不変的な更新を行う -
非同期ロジック、ランダム値の計算、その他の「副作用」を引き起こしてはならない
これらのルールの重要性と正しい実装方法については後ほど詳しく説明します。
Reducer関数内のロジックは通常、次の一連のステップに従います:
-
このアクションが対象かどうかを確認する
- 対象の場合、stateをコピーし、新しい値で更新して返す
-
対象外の場合、既存のstateを変更せずに返す
以下は各Reducerが従うべきステップを示した簡単な例です:
const initialState = { value: 0 }
function counterReducer(state = initialState, action) {
// Check to see if the reducer cares about this action
if (action.type === 'counter/incremented') {
// If so, make a copy of `state`
return {
...state,
// and update the copy with the new value
value: state.value + 1
}
}
// otherwise return the existing state unchanged
return state
}
Reducer内部では、新しいstateを決定するためにあらゆる種類のロジック(if/else、switch、ループなど)を使用できます。
Detailed Explanation: Why Are They Called 'Reducers?'
The Array.reduce() method lets you take an array of values, process each item in the array one at a time, and return a single final result. You can think of it as "reducing the array down to one value".
Array.reduce() takes a callback function as an argument, which will be called one time for each item in the array. It takes two arguments:
previousResult, the value that your callback returned last timecurrentItem, the current item in the array
The first time that the callback runs, there isn't a previousResult available, so we need to also pass in an initial value that will be used as the first previousResult.
If we wanted to add together an array of numbers to find out what the total is, we could write a reduce callback that looks like this:
const numbers = [2, 5, 8]
const addNumbers = (previousResult, currentItem) => {
console.log({ previousResult, currentItem })
return previousResult + currentItem
}
const initialValue = 0
const total = numbers.reduce(addNumbers, initialValue)
// {previousResult: 0, currentItem: 2}
// {previousResult: 2, currentItem: 5}
// {previousResult: 7, currentItem: 8}
console.log(total)
// 15
Notice that this addNumbers "reduce callback" function doesn't need to keep track of anything itself. It takes the previousResult and currentItem arguments, does something with them, and returns a new result value.
A Redux reducer function is exactly the same idea as this "reduce callback" function! It takes a "previous result" (the state), and the "current item" (the action object), decides a new state value based on those arguments, and returns that new state.
If we were to create an array of Redux actions, call reduce(), and pass in a reducer function, we'd get a final result the same way:
const actions = [
{ type: 'counter/incremented' },
{ type: 'counter/incremented' },
{ type: 'counter/incremented' }
]
const initialState = { value: 0 }
const finalResult = actions.reduce(counterReducer, initialState)
console.log(finalResult)
// {value: 3}
We can say that Redux reducers reduce a set of actions (over time) into a single state. The difference is that with Array.reduce() it happens all at once, and with Redux, it happens over the lifetime of your running app.
ストア(Store)
現在のReduxアプリケーションの状態は、ストアと呼ばれるオブジェクト内に保持されます。
ストアはReducerを渡して作成され、現在の状態値を返すgetStateメソッドを持ちます:
import { configureStore } from '@reduxjs/toolkit'
const store = configureStore({ reducer: counterReducer })
console.log(store.getState())
// {value: 0}
ディスパッチ(Dispatch)
Reduxストアにはdispatchメソッドがあります。状態を更新する唯一の方法は、store.dispatch()を呼び出してアクションオブジェクトを渡すことです。ストアはReducer関数を実行して新しい状態値を保存し、getState()を呼び出すことで更新された値を取得できます:
store.dispatch({ type: 'counter/incremented' })
console.log(store.getState())
// {value: 1}
アクションのディスパッチは、アプリケーション内で「イベントをトリガーする」と考えることができます。何かが発生したことをストアに通知し、Reducerはイベントリスナーとして機能します。Reducerは関心のあるアクションを検知すると、それに応じて状態を更新します。
セレクター(Selectors)
セレクターは、ストアの状態値から特定の情報を抽出する方法を知る関数です。アプリケーションが大きくなるにつれ、異なる部分で同じデータを読み取る必要がある場合にロジックの重複を防ぐのに役立ちます:
const selectCounterValue = state => state.value
const currentValue = selectCounterValue(store.getState())
console.log(currentValue)
// 2
中核概念と原則
Redux の設計思想は次の3つの核心概念に集約できます:
単一の信頼できる情報源(Single Source of Truth)
アプリケーションのグローバル状態は単一のストア内にオブジェクトとして保存されます。データは複数箇所で重複するのではなく、一箇所にのみ存在すべきです。
これにより状態変化時のデバッグや検査が容易になり、アプリケーション全体と連動するロジックを一元管理できます。
これはアプリ内の_すべての_状態が Redux ストアに入れなければならないという意味ではありません!状態がどこで必要かに基づいて、Redux に入れるか UI コンポーネントに残すかを判断してください。
状態は読み取り専用(State is Read-Only)
状態を変更する唯一の方法は、発生した事象を記述したオブジェクトであるアクションをディスパッチすることです。
これにより UI が誤ってデータを上書きすることを防ぎ、状態更新の理由を追跡しやすくなります。アクションはプレーンな JS オブジェクトなので、ログ記録、シリアライズ、保存が可能で、後でデバッグやテスト目的で再生できます。
純粋関数による変更(Changes are Made with Pure Reducer Functions)
アクションに基づいて状態ツリーを更新する方法を指定するために、リデューサー関数を記述します。リデューサーは前の状態とアクションを受け取り、次の状態を返す純粋関数です。他の関数と同様に、リデューサーを小さな関数に分割したり、共通タスク用の再利用可能なリデューサーを書いたりできます。
Redux アプリケーションデータフロー
先に「一方向データフロー」について説明しましたが、これはアプリを更新する次の一連のステップを表しています:
-
状態(State)は特定の時点におけるアプリの状態を記述する
-
UIはその状態に基づいてレンダリングされる
-
何かが発生すると(ユーザーのボタンクリックなど)、発生した内容に基づいて状態が更新される
-
UIは新しい状態に基づいて再レンダリングされる
Reduxに特化して、これらのステップをさらに詳細に分解できます:
-
初期設定:
- ルートReducer関数を使用してReduxストアが作成される
- ストアはルートReducerを一度呼び出し、戻り値を初期
stateとして保存する - UIが初めてレンダリングされるとき、UIコンポーネントはReduxストアの現在の状態にアクセスし、そのデータを使用して表示内容を決定する。また、状態が変更されたことを検知できるよう、今後のストア更新をサブスクライブする
-
更新:
- ユーザーのボタンクリックなど、アプリ内で何かが発生する
- アプリコードがReduxストアにアクションをディスパッチする(例:
dispatch({type: 'counter/incremented'})) - ストアは前回の
stateと現在のactionでリデューサー関数を再実行し、戻り値を新しいstateとして保存する - ストアは更新されたことをサブスクライブしている全UIパーツに通知する
- ストアからデータを必要とする各UIコンポーネントは、必要な状態の部分が変更されたかどうかを確認する
- データが変更されたことを確認した各コンポーネントは、新しいデータで再レンダリングを強制し、画面表示を更新する
このデータフローを視覚的に表現すると次のようになります:

学んだこと
- Reduxの目的は3つの原則に要約できます
- グローバルなアプリ状態は単一のストアに保持される
- ストアの状態はアプリの他の部分から読み取り専用である
- リデューサー関数はアクションに応答して状態を更新するために使用される
- Reduxは「単方向データフロー」のアプリ構造を使用する
- 状態(State)はある時点でのアプリの状態を記述し、UIはその状態に基づいてレンダリングされる
- アプリで何かが発生すると:
- UIがアクションをディスパッチする
- ストアがリデューサーを実行し、発生した内容に基づいて状態が更新される
- ストアが状態が変更されたことをUIに通知する
- UIは新しい状態に基づいて再レンダリングされる
次のステップ
これで、Reduxアプリのさまざまな部分を説明する主要な概念と用語について理解できたはずです。
次に、パート3: 状態、アクション、リデューサーで新しいReduxアプリケーションの構築を始めながら、これらの要素がどのように連携するかを見ていきましょう。