Django+Reactで学ぶプログラミング基礎(30): Reduxチュートリアル(概念とデータフローの詳細)

##### [前回] [Django+Reactで学ぶプログラミング基礎(29): Reduxチュートリアル(構成要素とデータフローの概要)](https://qiita.com/mingchun_zhao/items/59e1b271cbba92606c2b)

## はじめに

前回は、Reduxの概念とデータフローの概要を勉強しました。
今回は、Reduxの概念とデータフローを深掘りします。

## 今回の内容

[Reduxの概念とデータフロー](https://redux.js.org/tutorials/fundamentals/part-2-concepts-data-flow)

- バックグラウンドのコンセプト
- Reduxの用語
- コアコンセプトと原理
- Reduxアプリのデータフロー

## バックグラウンドのコンセプト


### 状態管理

#### Reduxカウンターコンポーネントの例

- コンポーネントのstateとなるカウンターを追跡し
- ボタンがクリックされるたびにカウンターをインクリメント
- 自己完結型のアプリ(self-contained app)で、下記要素を含む
    - state: アプリを動かす真実の源
    - view: 現在のstateに基づくUIの宣言型説明
    - actions: ユーザー入力により発生するイベント、またはstateを更新するトリガー
```
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)
    - stateに基づき、レンダリング
- ③actions
    - ユーザーのボタンクリックなどによりイベント発生すると、stateが更新される
- ④view(UI)
    - UIは新しいstateに基づき、再レンダリングされる

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2523098/c714be73-5f52-7faf-3579-79a56625dbe0.png)

※ 引用元: [一方向データフロー](https://redux.js.org/tutorials/fundamentals/part-2-concepts-data-flow)

#### 複数コンポーネントで同じstateを共有したい

- 候補案: 親コンポーネントに`state`をリフトアップ(移管)する
    - 解決できる場合もあるが、必ずしも役立つとは限らない
- 解決策: コンポーネントから共有stateを抽出し、コンポーネントツリー外側の中央に配置
    - コンポーネントツリーは大きな`view`となり、どのコンポーネントからも以下操作が可能
        - stateにアクセス
        - actionをトリガー

#### Reduxでstateの基本的な考え方

- state管理の概念を定義し、viewから分離
    - viewとstate間で独立性を維持
    - コード構造化と保守性を向上

- アプリのグローバルなstateを、同じ場所にまとめて保持
    - state更新時に従うべきパターンをあらかじめ定義し
        - コードを予測可能なものにする

#### イミュータビリティ(不変性)

- ミュータブルとは、変更可能であること
    - JavaScriptオブジェクトと配列は、デフォルトで変更可能
    - メモリ上で同じオブジェクト/配列への参照であっても、オブジェクトの内容を変更可能
```
const obj = { a: 1, b: 2 }
// オブジェクト自身を変えず、そのフィールドを変えられる
obj.b = 3

const arr = ['a', 'b']
// 配列自身を変えず、その要素を変えられる
arr.push('c')
arr[1] = 'd'
```

- イミュータブルとは、不変であること
    - Reduxで、すべてのstate更新がイミュータブルに行われる必要あり   
    - 値をイミュータブルに更新するには、既存のオブジェクト/配列のコピーを作成し、そのコピーを変更
        - 方法1: JavaScriptの配列/オブジェクトスプレッド演算子
        - 方法2: 元配列の新しいコピーを返す配列メソッドを使用
```
const obj = {
  a: {
    // 安全にobj.a.cを更新するためには, オブジェクトのコピーが必要
    c: 3
  },
  b: 2
}

const obj2 = {
  // スプレッド演算子で、オブジェクトをコピー
  ...obj,
  // aを書き換える
  a: {
    // obj.aをコピー
    ...obj.a,
    // cを書き換える
    c: 42
  }
}

const arr = ['a', 'b']
// 配列の新しいコピーを作成し, 末尾に`c`を追加
const arr2 = arr.concat('c')

// または、元の配列のコピーを作成
const arr3 = arr.slice()
// コピーを書き換える(ミューテート)
arr3.push('c')
```

## Reduxの用語

### Actions

- アプリで発生したことを説明するイベント
- プレーンなJavaScriptオブジェクト
- typeフィールド
    - actionを一意識別する、わかりやすい名前
    - 通常、`domain/eventName`のような文字列で記述
        - 最初の`domain`は、acitonが属する機能またはカテゴリ
        - 2番目の`eventName`は、発生した特定イベント
- payloadフィールド
    - 発生イベントの追加情報
```
const addTodoAction = {
  type: 'todos/todoAdded',
  payload: 'Buy milk'
}
```

### Reducer

- 現在のstateとactionを受け取り、stateの更新方法を決定し、新しいstateを返す関数
    - `(state、action) => newState`
    - 受信したaction(イベント)タイプに基づき、イベントを処理するイベントリスナーを決める

- Reducer関数が従うべきルール
    - stateの更新は、イミュータブルである必要あり
        - stateとaction引数を受け取り、新しいstateを計算するのみ
        - 既存のstateを変更してはいけない
        - 既存のstateをコピーし、コピーされた値に変更を加える
    - 非同期ロジックを実行してはいけない
    - ランダムな値を計算してはいけない

- Reducer関数内ロジックの手順
    - reducerが対応すべきactionの場合
        - stateのコピーを作成し、コピーを更新してから、返す
    - それ以外の場合
        - 既存のstateを変更せず、そのまま返す
```
const initialState = { value: 0 }

function counterReducer(state = initialState, action) {
  // reducerがactionを処理すべきか
  if (action.type === 'counter/incremented') {
    // stateのコピーを作成
    return {
      ...state,
      // コピーを新しい値に更新
      value: state.value + 1
    }
  }
  // さもなければ、既存stateをそのまま返す
  return state
}
```

### Store

- Reduxアプリのstateは、storeオブジェクトに存在
- `configureStore`メソッドにreducerを渡し実行することで、storeを作成
- `getState`メソッドを用いて、現在のstateを返す
```
import { configureStore } from '@reduxjs/toolkit'

const store = configureStore({ reducer: counterReducer })

console.log(store.getState())
// {value: 0}
```

### Dispatch

- stateを更新する唯一の手段は、
    - `store.dispatch`メソッドにactionオブジェクトを渡し、実行すること
- storeは、reducer関数を実行し、新しいstate値を保存
- `getState()`メソッドを用いて、更新されたstate値を取得
- アプリでは、actionのdispatchを`イベントのトリガー`と見做すことができる
    - 何かイベントが起こったら、storeにそれを伝える
    - reducerは、イベントリスナーのように振る舞う
        - 関連するactionを受け取ったら、それに応じてstateを更新
```
store.dispatch({ type: 'counter/incremented' })

console.log(store.getState())
// {value: 1}
```

### Selector

- storeのstate値から特定情報を抽出するための関数
- アプリが大きくなると、さまざまな部分で同じデータを読み取る必要が生じる
    - 共通関数化により、同じロジックの繰り返しを回避
```
const selectCounterValue = state => state.value

const currentValue = selectCounterValue(store.getState())
console.log(currentValue)
// 2
```

## コアコンセプトと原理

### 信頼できる唯一の情報源は、一ヵ所にまとまって保存されるstate

- アプリのグローバルstateは、単一のstoreにオブジェクトとして保存される
    - 特定のstateは、複数場所で複製されるのではなく、一つの場所にのみ存在する必要あり

- これにより、状況の変化に応じてアプリの状態をデバッグ/調査しやすくなる
    - アプリ全体とやり取りするために必要なロジックを一元化できる

- アプリで、すべてのstateがstoreに保存されるとは限らない
    - stateの一部がReduxに属するかUIコンポーネントに属するかを決定する必要あり

- stateは読み取り専用
    - stateを変更する唯一の方法は、何が起こったかを示すactionオブジェクトをdispatchすること
    - メリット1: UIが誤ってデータを上書きすることを回避
    - メリット2: stateの更新が発生した理由を簡単に追跡できる
    - actionはプレーンなJSオブジェクトであるため
        - ログに記録可能
        - シリアル化して保存可能
        - デバッグやテストの目的で再生可能

- stateの変更は、純粋(Pure)なreducer関数により行われる
    - reducer関数で、actionに基づきstateツリーを更新する方法を定義
    - reducer関数は、前のstateとactionを取り、次のstateを返す純粋関数(Pure Function)
        - reducerを小さな関数に分割し作業を支援したり
        - 一般的なタスク用に再利用可能なreducerを作成可能

## Reduxアプリのデータフロー

### `一方向データフロー`手順を詳細に分割

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2523098/cbca2fa1-8619-201e-7a4b-d356bf5c2c31.png)

※ 引用元: [Reduxのデータフロー](https://redux.js.org/tutorials/fundamentals/part-2-concepts-data-flow)

#### 初期設定フェーズ

- storeは、root reducer関数を使用し、作成される
    - storeはroot reducerを1回呼び出し、戻り値を初期状態として保存
    - UIが最初にレンダリングされるとき、UIコンポーネントはRedux storeの現在stateにアクセス
        - stateを使用し、何をレンダリングするか決定
        - store更新をsubscribeしておくと、stateの更新を検知できる

#### 更新フェーズ

- アプリで、ユーザーがボタンをクリックするなどイベント発生
    - アプリコードは、actionをRedux storeにdispatch
        - `dispatch({type: 'counter/incremented'})`
- storeは、前のstateと現在のactionで、reducer関数を再度実行
    - 戻り値を新しいstateとして保存
- storeは、subscribeされているUIに、store更新を通知
- storeからのstate値を必要とする各UIコンポーネントは、stateが変更されているか確認
- state更新を検知した各UIコンポーネントは、新しいstateで強制的に再レン​​ダリングする
    - 画面の表示内容が更新される

## おわりに

Reduxの概念とデータフローを深掘りしました。
次回も続きます。お楽しみに。

##### [次回] [Django+Reactで学ぶプログラミング基礎(31): Reduxチュートリアル(Todoアプリの要件定義と設計)](https://qiita.com/mingchun_zhao/items/9f379da807058a3d42f5)