LoginSignup
3
0

More than 1 year has passed since last update.

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

Last updated at Posted at 2022-06-18
[前回] Django+Reactで学ぶプログラミング基礎(29): Reduxチュートリアル(構成要素とデータフローの概要)

はじめに

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

今回の内容

Reduxの概念とデータフロー

  • バックグラウンドのコンセプト
  • 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

※ 引用元: 一方向データフロー

複数コンポーネントで同じ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

※ 引用元: Reduxのデータフロー

初期設定フェーズ

  • 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アプリの要件定義と設計)
3
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
0