Redux(Actions, Reducers, Store)による状態管理

Reduxによる状態管理の動作確認を行います。Actions, Reducers, Storeなどの役割を実際の処理を通じて確認します。

Reduxの3原則

動作確認をする前に、Reduxの3原則を復習しておきます。

https://redux.js.org/introduction/three-principlesにてReduxの3原則が説明されいています。

  1. Single source of truth
  2. State is read-only
  3. Changes are made with pure functions

Reducerは純粋関数でなければならないなどのReduxを利用する上での注意点が述べられています。

Reduxの動作確認

Reduxのみの動作確認にReactは不要なので、nodeで動作確認します。

reduxをインストール

npm install --save redux

動作確認用ソース

動作確認に利用したコードを示します。

実際に利用するときはファイルを分けて記述しますが、今回は動作確認が目的なので src/app.js にまとめて記述します。以下内容で構成されています。

  • Action Types を定義
  • Action Creators を定義
  • Reducers を定義
  • redux.createStore(Reducers, initialState)Store を生成
  • 動作確認
    • store.getState() で状態取得できること
    • store.dispatch(actionオブジェクト) で状態更新できること
const redux = require('redux')


/**************************************
 * Action Types
 **************************************/
// Count Actions
const INCREMENT_COUNT = 'INCREMENT_COUNT'
const DECREMENT_COUNT = 'DECREMENT_COUNT'
// Log Actions
const ADD_LOG         = 'ADD_LOG'
const DELETE_LOG      = 'DELETE_LOG'
const SET_LOG_LOADING = 'SET_LOG_LOADING'


/**************************************
 * Action Creators
 *
 * 戻り値
 *   actionオブジェクト(ユーザが行った操作を表現)
 **************************************/
// Counter Action Creators
const incrementCount = () => {
  return ({
    type: INCREMENT_COUNT,
  })
}
const decrementCount = () => {
  return ({
    type: DECREMENT_COUNT,
  })
}
// Log Action Creators
const addLog = (id, text) => {
  return ({
    type: ADD_LOG,
    payload: {
      id,
      text
    },
  })
}
const deleteLog = id => {
  return ({
    type: DELETE_LOG,
    payload: {
      id
    },
  })
}
const setLogLoading = () => {
  return {
    type: SET_LOG_LOADING,
  }
}


/**************************************
 * Reducers > 純粋関数
 *
 * 引数
 *   stateオブジェクト(変化前の状態)
 *   actionオブジェクト
 * 戻り値
 *   stateオブジェクト(変化後の状態)
 **************************************/
// Counter Reducer
const initialCounterState = 0
const counterReducer = (state = initialCounterState, action) => {
  switch (action.type) {
    case INCREMENT_COUNT:
      return state + 1
    case DECREMENT_COUNT:
      return state - 1
    default:
      return state
  }
}
// Log Reducer
const initialLogState = {
  logs: [],
  loading: false,
}
const logReducer = (state = initialLogState, action) => {
  switch (action.type) {
    case ADD_LOG:
      return {
        ...state,
        logs: [...state.logs, action.payload],
        loading: false
      }
    case DELETE_LOG:
      return {
        ...state,
        logs: state.logs.filter(log => log.id !== action.payload.id),
        loading: false
      }
    case SET_LOG_LOADING:
      return {
        ...state,
        loading: true
      }
    default:
      return state
  }
}
// Reducerを1つにまとめる
const rootReducer = redux.combineReducers({
  log: logReducer,
  counter: counterReducer
})


/**************************************
 * Store
 **************************************/
const initialState = {}
const store = redux.createStore(rootReducer, initialState)


/**************************************
 * 動作確認
 **************************************/
console.log('############ state(初期値) ############')

console.log(JSON.stringify(store.getState()))
console.log()

console.log('############ 動作確認(Counter state) ############')

store.dispatch(incrementCount())
console.log(JSON.stringify(store.getState()))

store.dispatch(incrementCount())
console.log(JSON.stringify(store.getState()))

store.dispatch(decrementCount())
console.log(JSON.stringify(store.getState()))
console.log()

console.log('############ 動作確認(Log state) ############')

store.dispatch(setLogLoading())
console.log(JSON.stringify(store.getState()))

store.dispatch(addLog(1, 'x'))
console.log(JSON.stringify(store.getState()))

store.dispatch(setLogLoading())
console.log(JSON.stringify(store.getState()))

store.dispatch(addLog(2, 'y'))
console.log(JSON.stringify(store.getState()))

store.dispatch(setLogLoading())
console.log(JSON.stringify(store.getState()))

store.dispatch(addLog(3, 'z'))
console.log(JSON.stringify(store.getState()))

store.dispatch(setLogLoading())
console.log(JSON.stringify(store.getState()))

store.dispatch(deleteLog(2))
console.log(JSON.stringify(store.getState()))

実行結果

$ node src/app.js 
############ state(初期値) ############
{"log":{"logs":[],"loading":false},"counter":0}

############ 動作確認(Counter state) ############
{"log":{"logs":[],"loading":false},"counter":1}
{"log":{"logs":[],"loading":false},"counter":2}
{"log":{"logs":[],"loading":false},"counter":1}

############ 動作確認(Log state) ############
{"log":{"logs":[],"loading":true},"counter":1}
{"log":{"logs":[{"id":1,"text":"x"}],"loading":false},"counter":1}
{"log":{"logs":[{"id":1,"text":"x"}],"loading":true},"counter":1}
{"log":{"logs":[{"id":1,"text":"x"},{"id":2,"text":"y"}],"loading":false},"counter":1}
{"log":{"logs":[{"id":1,"text":"x"},{"id":2,"text":"y"}],"loading":true},"counter":1}
{"log":{"logs":[{"id":1,"text":"x"},{"id":2,"text":"y"},{"id":3,"text":"z"}],"loading":false},"counter":1}
{"log":{"logs":[{"id":1,"text":"x"},{"id":2,"text":"y"},{"id":3,"text":"z"}],"loading":true},"counter":1}
{"log":{"logs":[{"id":1,"text":"x"},{"id":3,"text":"z"}],"loading":false},"counter":1}

イメージ図

動作確認に利用した処理のイメージ図を描いてみました。

703-react-redux_image.png

参考