Django+Reactで学ぶプログラミング基礎(29): Reduxチュートリアル(構成要素とデータフローの概要)

##### [前回] [Django+Reactで学ぶプログラミング基礎(28): Reduxチュートリアル(準備と概要)](https://qiita.com/mingchun_zhao/items/993ff45e5fed918aa8e3)

## はじめに

前回は、チュートリアルの概要と、なぜReduxが必要か、
を勉強しました。
今回は、Reduxアプリの構成要素と、データフローの概要を、
サンプルを通して理解します。

## 今回の内容

[Reduxの基本](https://redux.js.org/tutorials/fundamentals/part-1-overview#redux-basics)

- Reduxアプリの構成要素
- Reduxアプリのデータフロー
- Reduxコアをサンプルアプリで理解

## Reduxアプリの構成要素

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2523098/72c64942-0b37-f2ab-cc60-c719e2d09c1e.png)

※ 引用元: [Reduxアプリを介したデータフロー](https://redux.js.org/tutorials/fundamentals/part-1-overview#redux-basics)

- Store
    - すべてのReduxアプリの中心
    - アプリのグローバル状態を保持するコンテナ
    - いくつかの特別な機能を備えたJavaScriptオブジェクト

- State/View(UI)/Reducer/Actions
    - State
        - アプリの状態を保持する唯一の手段
        - 特定時点のアプリ状態を表す
    - View
        - 現在の状態に基づく、UIの宣言型説明
        - UIは状態に基づきレンダリングされる
    - Reducer
        - storeで、reducer関数を実行し、新しいstateを計算
    - Actions
        - アプリで、ユーザー入力による、発生イベントおよび状態更新のトリガー
        - ユーザーがボタンをクリックするなどアクションを起こすと
            - stateが、発生内容に基づき更新される
            - UI(view)は、新しい状態を読み取り、再レンダリングされる
            - actionが、storeにdispatchされる
            - storeが、reducer関数で新しいstateを計算

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

- UIをクリックし、イベント発生
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2523098/f71c542c-b088-7a3f-2cbc-6a454ea5c625.png)

- ActionがStoreにDispatchされる
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2523098/a7a8019b-954a-d544-bd07-70e162fafd29.png)

- ReducerがStateを計算し、その結果によりUIが更新される
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2523098/2d48ac76-a592-216f-dd81-3225b9a4e3bf.png)

※ 引用元: [Reduxアプリを介したデータフロー](https://redux.js.org/tutorials/fundamentals/part-1-overview#redux-basics)

- アプリでアクションが発生したら
    - 発生イベントを表すプレーン(plain)なactionオブジェクトを作成
        - actionをstoreにdispatchし、何が発生したか通知
            - storeはroot reducer関数を実行し、古いstateとactionに基づき、新しいstateを計算
                - storeはsubscriberにstateが更新されたことを通知
                    - view(UI)が、subscriberにより新しいデータで更新される

- 上述フローが、storeに保持されているstateを変更する唯一の方法
    - ※ storeに保持されたstateを直接変更してはいけない

## Reduxコアをサンプルアプリで理解

### 環境準備

- [Redux開発環境の構築](https://qiita.com/mingchun_zhao/items/095a7d7ee5ac58cc0f51#redux%E9%96%8B%E7%99%BA%E7%92%B0%E5%A2%83%E3%81%AE%E6%A7%8B%E7%AF%89)

- [React-Reduxアプリを作成](https://qiita.com/mingchun_zhao/items/095a7d7ee5ac58cc0f51#react-redux-%E3%82%A2%E3%83%97%E3%83%AA%E3%82%92%E4%BD%9C%E6%88%90)

- VS Codeで、`ファイル`メニューから`フォルダーを開く...`
    - 上記作成したアプリのフォルダーを指定
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2523098/5bc1a6db-ff6c-111e-727f-ccee2f035d65.png)

### アプリコード作成

-  `src`フォルダーに新規ファイル`index.html`を追加
```src/index.html
<!DOCTYPE html>
<html>
  <head>
    <title>Redux basic example</title>
    <script src="https://unpkg.com/redux@latest/dist/redux.min.js"></script>
  </head>
  <body>
    <div>
      <p>
        Clicked: <span id="value">0</span> times
        <button id="increment">+</button>
        <button id="decrement">-</button>
        <button id="incrementIfOdd">Increment if odd</button>
        <button id="incrementAsync">Increment async</button>
      </p>
    </div>
    <script>
      // Define an initial state value for the app
      const initialState = {
        value: 0
      };

      // Create a "reducer" function that determines what the new state
      // should be when something happens in the app
      function counterReducer(state = initialState, action) {
        // Reducers usually look at the type of action that happened
        // to decide how to update the state
        switch (action.type) {
          case "counter/incremented":
            return { ...state, value: state.value + 1 };
          case "counter/decremented":
            return { ...state, value: state.value - 1 };
          default:
            // If the reducer doesn't care about this action type,
            // return the existing state unchanged
            return state;
        }
      }

      // Create a new Redux store with the `createStore` function,
      // and use the `counterReducer` for the update logic
      const store = Redux.createStore(counterReducer);

      // Our "user interface" is some text in a single HTML element
      const valueEl = document.getElementById("value");

      // Whenever the store state changes, update the UI by
      // reading the latest store state and showing new data
      function render() {
        const state = store.getState();
        valueEl.innerHTML = state.value.toString();
      }

      // Update the UI with the initial data
      render();
      // And subscribe to redraw whenever the data changes in the future
      store.subscribe(render);

      // Handle user inputs by "dispatching" action objects,
      // which should describe "what happened" in the app
      document
        .getElementById("increment")
        .addEventListener("click", function () {
          store.dispatch({ type: "counter/incremented" });
        });

      document
        .getElementById("decrement")
        .addEventListener("click", function () {
          store.dispatch({ type: "counter/decremented" });
        });

      document
        .getElementById("incrementIfOdd")
        .addEventListener("click", function () {
          // We can write logic to decide what to do based on the state
          if (store.getState().value % 2 !== 0) {
            store.dispatch({ type: "counter/incremented" });
          }
        });

      document
        .getElementById("incrementAsync")
        .addEventListener("click", function () {
          // We can also write async logic that interacts with the store
          setTimeout(function () {
            store.dispatch({ type: "counter/incremented" });
          }, 1000);
        });
    </script>
  </body>
</html>
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2523098/aef07e47-2603-e73d-aa38-9fc54bf48233.png)

### ブラウザで、カウンターアプリを使ってみる
- F5を押してアプリを実行
    - デバッガーとして`Web App(Chrome)`を選択
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2523098/a1980e50-6020-d966-fb9a-bcc3fd1704d0.png)

- ブラウザで、以下ボタンでさまざまなカウント操作を行ってみる
    - `+`をクリック: 1増やす
    - `-`をクリック: 1減らす
    - `Increment if odd`をクリック: 奇数時のみ増やす
    - `Increment async`をクリック: 1秒遅延と伴う非同期加算

### コード説明

#### State/Actions/Reducers

- まず、カウンターの状態を表すstateの初期値を定義
    - Reduxアプリでstateの構成
        - stateのroot部分として、JSオブジェクトを持つ
            - サンプルでは、`initialState `
            - そのJSオブジェクト内に他の値が含まれる
```src/index.html
// Define an initial state value for the app
const initialState = {
  value: 0
}
```

- 次に、reducer関数を定義
    - reducerは、二つの引数を受け取る
        - `state`: 現在の状態を表す
        - `action`: 何が起こったかを示す
    - Reduxアプリ起動時、まだstateがないため
        - reducerのデフォルト値として`initialState`を指定
    - actionオブジェクトのtypeフィールド
        - actionの一意の名前を表すため、意味が分かりやすい名前に
            - 例えば、`カウンターを増やす`actionのtype
                - `counter/incremented`と記述
        - actionのtypeに基づいて、返却されるstateが異なる
            - stateが変更された場合、新しいstateオブジェクトを返す
            - stateが変更されていない場合、既存のstateオブジェクトを返す
                - ※ stateは、イミュータブルに更新
                    - 元のstateオブジェクトを直接変更せず、既存のstateをコピーしそれを更新
```src/index.html
// Create a "reducer" function that determines what the new state
// should be when something happens in the app
function counterReducer(state = initialState, action) {
  // Reducers usually look at the type of action that happened
  // to decide how to update the state
  switch (action.type) {
    case 'counter/incremented':
      return { ...state, value: state.value + 1 }
    case 'counter/decremented':
      return { ...state, value: state.value - 1 }
    default:
      // If the reducer doesn't care about this action type,
      // return the existing state unchanged
      return state
  }
}
```

#### store

- Reduxライブラリの`createStore`APIを呼び出し、storeインスタンスを作成
    - reducer関数を`createStore`に渡す
    - `createStore`はreducer関数を使用し
        - 初期値を持つstateを生成
        - 将来更新時、stateを計算
```src/index.html
// Create a new Redux store with the `createStore` function,
// and use the `counterReducer` for the update logic
const store = Redux.createStore(counterReducer)
```

#### view(UI)

- UIは、画面に既存のstateを表示
    - ユーザーがアクションを起こすと
        - アプリはそのデータを更新してから、新しい値でUIを再レンダリング
    - `store.getState()`メソッドを使用し、storeから最新stateを取得
        - その値を用いてUIを更新/表示
    - `store.subscribe()`メソッドを呼び出し、storeが更新されるたびに呼び出されるsubscriberコールバック関数を渡す
        - サンプルでは、`render`関数をsubscriberとして渡している
            - storeが更新されるたびに、UIを最新の値で更新可能
    - Redux自体は、どこでも使用できるスタンドアロンのライブラリ
        - 任意のUIレイヤーでReduxを使用可能
```src/index.html
// Our "user interface" is some text in a single HTML element
const valueEl = document.getElementById('value')

// Whenever the store state changes, update the UI by
// reading the latest store state and showing new data
function render() {
  const state = store.getState()
  valueEl.innerHTML = state.value.toString()
}

// Update the UI with the initial data
render()
// And subscribe to redraw whenever the data changes in the future
store.subscribe(render)
```

#### actionをdispatch

- actionオブジェクトを作成し、storeにdispatchすることで、ユーザー入力に応答
    - `store.dispatch(action)`を呼び出すと
        - storeはreducerを実行し、更新後のstateを計算
        - subscriberを実行し、UIを更新
    - サンプルでは、以下のような複数パターンでactionをdispatchし、reducerに計算させる
        - 現在のカウンター値から加算または減算
        - カウンター値が奇数の場合のみ加算
        - 1秒遅延後に非同期に加算
```src/index.html
// Handle user inputs by "dispatching" action objects,
// which should describe "what happened" in the app
document.getElementById('increment').addEventListener('click', function () {
  store.dispatch({ type: 'counter/incremented' })
})

document.getElementById('decrement').addEventListener('click', function () {
  store.dispatch({ type: 'counter/decremented' })
})

document
  .getElementById('incrementIfOdd')
  .addEventListener('click', function () {
    // We can write logic to decide what to do based on the state
    if (store.getState().value % 2 !== 0) {
      store.dispatch({ type: 'counter/incremented' })
    }
  })

document
  .getElementById('incrementAsync')
  .addEventListener('click', function () {
    // We can also write async logic that interacts with the store
    setTimeout(function () {
      store.dispatch({ type: 'counter/incremented' })
    }, 1000)
  })
```

## おわりに

Reduxアプリの構成要素とデータフローを、
サンプルを通じて勉強しました。
次回も続きます。お楽しみに。

##### [次回] [Django+Reactで学ぶプログラミング基礎(30): Reduxチュートリアル(概念とデータフローの詳細)](https://qiita.com/mingchun_zhao/items/c9ea1251cf66f2694e6b)