Redux基礎

redux基礎

  • 登場経緯 → SPAが複雑になってきてstate管理が辛くなってきた
  • state(状態)を管理するための手法(のフレームワーク)
  • Reduxは3原則に則って状態変化の流れを制限する
    • 1 Single source of truth
    • 2 State is read-only
    • 3 Mutations are written as pure functions

三原則

Single source of truth
  • 一つのソースが真実(直訳)
  • アプリの状態は一つのオブジェクトで作られ、一つのストアに保存
State is read only
  • 状態(STATE)の変更はACTIONを起こした時のみ
  • 変更は順番に行う
Mutations are written as pure functions
  • 変更を全てReducer(つまり純粋な関数)で書くこと

Action

  • イベントとそのイベントで利用するデータを定義
  • typeは必須プロパティ
    • Actionの中身(データ)はできるだけ小さい方が良い
    • データそのものでなくキーを持たせるなど
    • 1
      2
      3
      4
      5
      const ADD_TODO = 'ADD_TODO';
      {
      type: ADD_TODO,
      text: 5
      }

Action Creators

  • Actionを作ってreturnする関数
  • 1
    2
    3
    4
    5
    6
    addTodo = (text) => {
    return {
    type: ADD_TODO,
    text
    }
    }

Stateの型の設計方法

  • UIの状態とデータはできるだけ分ける
  • Todoアプリの場合、Stateで保持るもの
    • 表示、非表示するかどうか
    • todoリスト
  • 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    {
    visibilityFilter: 'SHOW_ALL',
    todos: [
    {
    text: 'hoge',
    completed: true,
    },
    {
    text: 'fuga',
    completed: false,
    }
    ]
    }
  • アプリのStateはDBのように考える

  • できるだけネストせず分かりやすくして、オブジェクトにIDをkeyとしてもたせる
  • 他のエンティティ(テーブル、データのまとまり)からはkeyを指定してデータを参照

Reducer

  • 変更前の状態に対してアクションを起こして、変更後の状態を返す
  • 純粋関数
    • 引数を変更不可
    • APIやルーティングなど利用して副作用を起こさない
    • 純粋関数しか呼ばない
  • 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    /*actionファイルで変数をexportして他ファイルでimportできるようにしておくっぽい*/
    import {
    ADD_TODO,
    TOGGLE_TODO,
    SET_VISIBILITY_FILTER,
    VIsibilityFilters,
    } from './actions';

    /*Stateの初期値を作成(Reduxが最初にStateをundefinedとしてReducerを呼ぶので)*/
    const initialState = {
    visibilityFIlter: VisibilityFilters.SHOW_ALL,
    todos: []
    }

    /*action creater*/
    todoApp = (state = initialState, actino) => {
    switch (action.type) {
    case SET_VISIBLITY_FILTER:
    /*新しいStateとしてオブジェクトの参照を切って作成*/
    return Object.assign({}, state, {
    visibilityFilter: action.filter
    }
    case ADD_TODO:
    return Object.assign({}, state, {
    /*既存の配列を全部展開して末尾に新しい要素を追加した配列*/
    todos: [
    ...state.todos,
    {
    text: action.text,
    completed: false
    }
    ]
    })
    case TOGGLE_TODO:
    return Object.assign({}, state, {
    todos: state.todos.map((todo, index) => {
    /*action.indexと同じindexのプロパティだけ参照切りしてcompletedの値を上書きしたプロパティに変更*/
    if (index === action.index) {
    return Object.assign({}, todo, {
    completed: !todo.completed
    })
    }
    return todo;
    })
    default:
    return state;
    }
    }
  • Object.assignの挙動

    • 列挙可能な値をターゲットプロパティにコピーする
    • 1
      2
      3
      4
      5
      // object1に追加される
      const object1 = { a: 1, b: 1 };
      Object.assign(object1,{ c: 3 });
      //ディープコピー(参照切り)される。同じプロパティ(キー)があれば上書きされる
      Object.assign({},object1,{ c: 4 });

Reducer Composition

  • 上の関数を各stateごとの関数に分割できる
  • 分割したものをreducer composition(reducer構成要素?合成?)という
  • 共通するオブジェクトを返すものは関数を分割して見やすく?する
  • 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    //上記 action creater から分割したtodoを操作する関数
    todo = (state = [], action) {
    switch (action.type) {
    case ADD_TODO:
    return [
    ...state,
    {
    text: action.text,
    completed: false
    }
    ]
    case TOGGLE_TODO:
    return state.map((todo, index) => {
    if(index === action.index) {
    return Object.assign({}, todo, {
    completed: !todo.completed
    })
    }
    return state
    })
    default:
    return state
    }
    }

    //上記 action creater から分割したtodoを操作する関数
    visibilityFilter = (state = SHOW_ALL, action) => {
    switch(action.type) {
    case SET_VISIBILITY_FILTER:
    return action.filter;
    return state;
    }
    default:
    return state;
    }

    //action creater
    //stateの初期値は、各action composition(?)からreturnされるので空のオブジェクトでいい
    //新しいオブジェクトをreturnするので参照切りしなくていい
    todoApp = (state = {}, action) => {
    return {
    visiblityFilter: visibilityFilter(state.visibilityFilter, action)
    todos: todos(state.todos, action)
    }
    }
    • reduxモジュールにあるcombineReducers()を使用すると上記のtodoAppと同じ処理を実行できる
    • combineReducerのコード
      • 「Reducerをループさせながら各Reducerを実行した結果を集約」
    • 1
      2
      3
      4
      5
      6
      import { combineReducer } from 'redux';

      const todoApp = combineReducers({
      visibilityFilter,
      todos
      })

Store

  • Storeは常に一つ
  • アプリの状態を一つのオブジェクトとして保持、管理する
  • ActionとReducerを繋げる役割
  • getState()でstateを参照
  • dispatch()でstateの更新
  • subscribe(listener)でリスナを登録

    • 返される関数で登録解除
  • 作成方法

  • 1
    2
    3
    4
    import { createStore } from 'redux';
    import todoApp from './reducers';

    let store = createStore(todoApp)

参考

todo