Skip to content

5-2. redux: Store, Action, Action Creator, Reducer, Dispatch #35

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
sumakokima2 opened this issue Apr 23, 2020 · 12 comments
Open

5-2. redux: Store, Action, Action Creator, Reducer, Dispatch #35

sumakokima2 opened this issue Apr 23, 2020 · 12 comments

Comments

@sumakokima2
Copy link
Owner

sumakokima2 commented Apr 23, 2020

Reduxとは

スクリーンショット 2020-05-12 10 40 39
(動画ではないです、静止画です)

  • State management library : Stateで管理するライブラリだよ
  • Makes creating complex applications easier : 複雑なアプリを簡単につくれる
  • Not required to create React App : ReactAppを作る上で必須ではない
  • Not explicitly designed to work with React : React で使われるための明示的なデザインはされていないよ

Fluxと類似点、相違点

スクリーンショット 2020-05-12 10 47 07

類似点

・一方通行のデータ管理
・更新ロジックを一カ所にまとめている(FluxならStore, ReduxならReducer)
・アプリケーションが状態を直接変更することはなく、状態の変更はアクションですべておこなわれる

相違点

・Reduxにはdispatcherがない。(Reducerで担う)
・Reduxでは状態のオブジェクトに変更を行なうことはない。新しい状態オブジェクトを作る。

イメージをつかむ

↓↓↓このGIFとてもわかりやすいです↓↓↓
issueに貼り付けられなくて残念。
https://camo.githubusercontent.com/5aba89b6daab934631adffc1f301d17bb273268b/68747470733a2f2f73332e616d617a6f6e6177732e636f6d2f6d656469612d702e736c69642e65732f75706c6f6164732f3336343831322f696d616765732f323438343535322f415243482d5265647578322d7265616c2e676966

語句の整理

Action Creator

const AAA = (引数) => { return( ***"ACTION"*** ); }; 
で記述されます。
Actionをリターンします。

Action

Action Creatorのreturn内に記載されるオブジェクトです。
{type: ******, paylpad(好きな値でOK): ********} の2つの値が必須です。

**・type ** : Actionの名前と考えていいと思います。大文字で書く、スペースは使わず _ (アンダースコアでつなぐ)ぐらいのルールしかありません。
のちにReducerActionの種類の場合わけに使います。
**・payload(好きな値でOK) **:のちにReducerで必要とする値(変数/引数)をここに宣言します。
nameだったり、amountだったり、heightだったりいろいろです。引数を使う時は、必ずAction Creatorのアロー関数で引数の宣言をしましょう。

なおpayload内を
{type: ******, paylpad: {****, ****}} というふうに配列で書いても大丈夫。

memo:勉強

{
  type: SET_VISIBILITY_FILTER,
  filter: SHOW_COMPLETED
}

Dispatch

日本語サイトでは「Dispatch:発火」と訳されていますが、「発信」「実行」と訳した方がいいと思います。
Dispatchは後述するStoreメソッドで行います。
store.dispatch(*Action Creator名*('引数1', '引数2', ,,,,,,,,));

こうすると、Action がReducerに渡されてReducerの関数が実行されます。

Reducer

Actionが発信されると、自動で呼び出される関数部分です。
Stateの管理を行います。

各関数内でif だったりcaseでActionのtypeを判定します。判定結果がtrueだったら return Aを、falseだったらreturn Bを返す といった書き方になります。
なお、上記return Areturn Bはいずれも** stateに上書きする内容 **を書きます。
Reducer内でやってはいけないことは、以下。
・入力値の変更
・API呼び出しやルーティングの移行などの副作用を伴う操作
・ピュアじゃない関数の呼び出し(例:Date.now() や Math.random())

Store

GIFのスクリーンショット

上の画像をみてもらうとなんとなくわかると思います。
dispatchでreducerを実行して、stateを変更管理する母体です。
storeには4つのメソッドがあります。

・getState() :stateの状態を確認
・dispatch(action) :actionを発信してreducerを実行する
・subscribe(listener) :勉強中
・replaceReducer(nextReducer) :高度なやつらしい。しばらくノータッチ。

以下のサンプルで作成する物

  • Umedyの動画のものです。codepenで書いています。

保険会社(Insurance company)の請求とか、新規加入とかを扱う
スクリーンショット 2020-05-14 12 22 03

ここでは「保険会社」の契約、請求、会計をベースとするAPPをつくります。つくりながらの流れをみながら学んだ方がわかりやすいと思います。

流れ:
ユーザがフォームで名前とお金を契約。
保険会社のリポジトリに名前がなければ新規契約としてデータを追加したり、解約の請求をされたらリポジトリから名前データを消したりします。
また、お金を請求されたら払ったり、契約だったらリポジトリにお金を追加したり という流れです。

@sumakokima2
Copy link
Owner Author

sumakokima2 commented May 14, 2020

  1. まずAction Creatorを作る
    Create Policy とは「契約」です。
    「契約」には通常、「名前」と「預けるお金」が必要なので、payloadにはname amountを記載します。
const createPolicy = () =>{
  return{           
/* ------------ここの部分がAction---------- */
      type: 'CREATE_POLICY',  //一般に大文字とアンダースコアで書く
      payload : {        //payload はActionが提供するコンテクストの情報を書く
        name :'Alex',
        amount :20
      }
/* ------------ここまで---------- */
  };
};

@sumakokima2
Copy link
Owner Author

  1. 上のやり方だと、payloadの値が固定になってしまうので、変数にしましょう。
const createPolicy = (name, amount) =>{      //この部分に引数
  return{           
/* ------------ここの部分がAction---------- */
      type: 'CREATE_POLICY',  
      payload : {        
        name : name,     //引数に変更
        amount : amount    //引数に変更
      }
/* ------------ここまで---------- */
  };
};

@sumakokima2
Copy link
Owner Author

sumakokima2 commented May 14, 2020

  1. 同様にAction Creatorを増やしていきます。ここで注意したいのは、一つのActioncreatorは1つのJSオブジェクト(Action)のみ返すこと。これがReduxだとデータ管理がシンプルになると言われる要素の一つです。
const createPolicy = (name, amount) =>{      //この部分に引数
  return{           
      type: 'CREATE_POLICY',  
      payload : {        
        name : name,     //引数に変更
        amount : amount    //引数に変更
      }
  };
};

/* ------------ここ以下を追加したよ---------- */
// 契約解消 解消のためには名前さえわかればそのデータを消せるので、pauloadは「name」だけでok
const deletePolicy = (name) => {
  return {
    type: 'DELETE_POLICY',
    payload: {
      name: name
    }
  };
};

// 請求 こちらはお金を会社からもらったりもらわなかったりなので、payloadには「名前」とその人物が扱う「お金」をかきます。
const createClaim = (name, amountOfMoneyToCollect) => {
  return {
    type: 'CREATE_CLAIM',
    payload: {
      name: name,
      amountOfMoneyToCollect: amountOfMoneyToCollect
    }
  };
};

@sumakokima2
Copy link
Owner Author

  1. Diepatch
    dispatchはすきっぷ。dispatchはあくまでアクションを受けたもののレシーバーで次につなげる役割しかなく、これはreduxライブラリに内包されている。つまり、ここをいじる必要はない!はず

@sumakokima2
Copy link
Owner Author

sumakokima2 commented May 14, 2020

  1. Reducer
    Action Creator が作ったActionに対して、Stateの変更/保留を伴う実際になにをするのかを割り振るところ。

4.1 Reducerの引数
Reducerでは引数の順番が大切です。
1つめの引数(oldListOfClaims):中央リポジトリから、必要となる元の情報をひろうためのもの。
2つめの引数(action):Actionの内容に応じて、何らかの形でState(リポジトリ)を更新する必要があるので、ここにactionを引数としてかきます。

const claimsHistory = (oldListOfClaims=[], action) =>{
};

(★)配列に新しいデータを追加する時
❌ arr.push(new_data); はだめです。これだと、oldListOfClaims配列が消滅して配列の個数がarrに代入されてしまいます
◎ [...arr, new_data] がOK。既存の配列を新しい配列に展開して、それに追加する形にしないといけないみたいです

@sumakokima2
Copy link
Owner Author

sumakokima2 commented May 14, 2020

4.2 何種類のReducerが必要?

今回必要なのは、[過去データの参照部門][会計部門][契約部門]

  1. [過去データの参照]
    スクリーンショット 2020-05-14 12 35 37
     「リポジトリの請求リスト」と「action」を読み込む。action typeがCREATE_CLAIM(請求)の場合、過去のデータを参照して未登録のときはPayloadのデータを参考に「リポジトリの請求データ」に今回のActionのデータを追加してリターン

  2. [会計]
    スクリーンショット 2020-05-14 12 36 07
     「預金(保険会社が持っているお金)」と「action」を読み込む。
     actionが「請求(Claim)」のとき:「預金」から必要なお金を引出しましょう
     actionが「請求(Claim)」でないとき:
      契約OK→「お金」を「預金」に追加してリターン
      契約NG→なにもしないでリターン

  3. [契約]
    スクリーンショット 2020-05-14 12 35 50
     「リポジトリの名前リスト」と「action」を読み込む。
     Action typeが新規契約or契約解除でないとき:
      なにもしない
     Action typeが新規契約or契約解除のとき:
      新規契約→「リポジトリの名前リストに追加」してリターン
      契約解除→「リポジトリの名前リストから削除」してリターン

これらに合わせてつくっていきます。

@sumakokima2
Copy link
Owner Author

まずは、[過去データの参照]

const claimsHistory = (oldListOfClaims=[], action) =>{ 
if (action.type === 'CREATE_CLAIM') {  //今回のサンプルの場合、CREATE_CLAIMは新しいデータを追加という意味です
    return [...oldListOfClaims, action.payload]; //(★後述)
  }
  
//action typeがCREATE_CLAIMでないときは、なにもしません〜
  return oldListOfClaims;
}

(★)oldListOfClaims引数の渡し方
実は、const claimsHistory = (oldListOfClaims, action) =>{だと、最初のリポジトリデータがundefinedだったときにerrorになります。
これを回避するために、
const claimsHistory = (oldListOfClaims=[], action) =>{
とかきませう。

@sumakokima2
Copy link
Owner Author

[会計部門]

const accounting = (bagOfMoney = 100, action) => {  //★
  if (action.type === 'CREATE_CLAIM') {
    return bagOfMoney - action.payload.amountOfMoneyToCollect;
  } else if (action.type === 'CREATE_POLICY') {
    return bagOfMoney + action.payload.amount;
  }
  
  return bagOfMoney;
};

*bagOfMoney を100とここで定めた理由は、初期値を設定する必要があったからです。
今回は初期値をreducerで100と定めました。

@sumakokima2
Copy link
Owner Author

sumakokima2 commented May 14, 2020

[契約部門]

const policies = (listOfPolicies = [], action) => {
  if (action.type === 'CREATE_POLICY') {
    return [...listOfPolicies, action.payload.name];
  } else if (action.type === 'DELETE_POLICY') {
    return listOfPolicies.filter(name => name !== action.payload.name); // (★)
  }
  
  return listOfPolicies;
};

(★)ここでは、actionがDELETE_POLICYだったときに該当のnameを削除したリストをリターンします。
このとき、filterメソッドを使います。
Arr.filter(data => data !== data1)
例えばArr[1,2,3,4]配列において、data1(ex: 2)とすると、
data !==2をArrから削除した新しい配列を返してくれます。

const arr = [1,2,3,4,5]
undefined
arr
(5) [1, 2, 3, 4, 5]
arr.filter(data => data!==2)
(4) [1, 3, 4, 5]

@sumakokima2
Copy link
Owner Author

sumakokima2 commented May 14, 2020

  1. これまでバラバラに書いてきたAction CreatorとReducerをまとめましょう
    ここでは、ReduxのライブラリにあるcreateStorecombineReducersを使います

なお、Storeの役割はです。こことreducerをつなぐとことで、reduxのサイクルは完成します。

stateを保持する
stateへアクセスするためのgetState()を提供する
stateを更新するためのdispatch(action)を提供する
リスナーを登録するためのsubscribe(listener)を提供する

const { createStore, combineReducers } = Redux;

const ourDepartments = combineReducers({
  accounting: accounting,   //★
  claimsHistory: claimsHistory,
  policies: policies
});

createStore

const store = createStore(ourDepartments);

この2つを追加することで、APPのStoreが完結しました。あとは、Storeのメソッドを使えばデータを回せます。

//★今回は、

  accounting: accounting,   
  claimsHistory: claimsHistory,
  policies: policies

のように同じ名前を左右に使いました。
もちろんここを

  hahahaha: accounting,   
  hihihihi: claimsHistory,
  policies: policies

のように変更することもできますが、次に書くstore.getState()をコンソールした時に名前がややこしくなってしまうのでお勧めしません。

@sumakokima2
Copy link
Owner Author

  1. DispachとStoreメソッドを使う
store.dispatch(createPolicy('Alex', 20));
store.dispatch(createPolicy('Jim', 30));
store.dispatch(createPolicy('Bob', 40));

store.dispatch(createClaim('Alex', 120));
store.dispatch(createClaim('Jim', 50));
store.dispatch(deletePolicy('Bob'));`

console.log(store.getState()); //現在のstateを取得

@sumakokima2
Copy link
Owner Author

  1. 全コード
console.clear();

// People dropping off a form (Action Creators)
const createPolicy = (name, amount) => {
  return { // Action (a form in our analogy)
    type: 'CREATE_POLICY',
    payload: {
      name: name,
      amount: amount
    }
  };
};

const deletePolicy = (name) => {
  return {
    type: 'DELETE_POLICY',
    payload: {
      name: name
    }
  };
};

const createClaim = (name, amountOfMoneyToCollect) => {
  return {
    type: 'CREATE_CLAIM',
    payload: {
      name: name,
      amountOfMoneyToCollect: amountOfMoneyToCollect
    }
  };
};


// Reducers (Departments!)
const claimsHistory = (oldListOfClaims = [], action) => {
  if (action.type === 'CREATE_CLAIM') {
    // we care about this action (FORM!)
    return [...oldListOfClaims, action.payload];
  }
  
  // we don't care the action (form!!)
  return oldListOfClaims;
};

const accounting = (bagOfMoney = 100, action) => {
  if (action.type === 'CREATE_CLAIM') {
    return bagOfMoney - action.payload.amountOfMoneyToCollect;
  } else if (action.type === 'CREATE_POLICY') {
    return bagOfMoney + action.payload.amount;
  }
  
  return bagOfMoney;
};

const policies = (listOfPolicies = [], action) => {
  if (action.type === 'CREATE_POLICY') {
    return [...listOfPolicies, action.payload.name];
  } else if (action.type === 'DELETE_POLICY') {
    return listOfPolicies.filter(name => name !== action.payload.name);
  }
  
  return listOfPolicies;
};

const { createStore, combineReducers } = Redux;

const ourDepartments = combineReducers({
  accounting: accounting,
  claimsHistory: claimsHistory,
  policies: policies
});

const store = createStore(ourDepartments);

store.dispatch(createPolicy('Alex', 20));
store.dispatch(createPolicy('Jim', 30));
store.dispatch(createPolicy('Bob', 40));

// store.dispatch(createClaim('Alex', 120));
// store.dispatch(createClaim('Jim', 50));

// store.dispatch(deletePolicy('Bob'));

console.log(store.getState());

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant