Reduxによる状態管理の動作確認を行います。Actions, Reducers, Storeなどの役割を実際の処理を通じて確認します。
目次
Reduxの3原則
動作確認をする前に、Reduxの3原則を復習しておきます。
https://redux.js.org/introduction/three-principles にてReduxの3原則が説明されいています。
- Single source of truth
- State is read-only
- Changes are made with pure functions
Reducerは純粋関数でなければならないなどのReduxを利用する上での注意点が述べられています。
関数(純粋関数, 再帰関数, 高階関数, クロージャー)
一歩進んだ関数の使い方について解説します。「再利用しやすい関数」「処理を自由に変更できる関数」を作るために、必要な知識となります。
Reduxの動作確認用コード
Reduxのみの動作確認にReactは不要なので、nodeで動作確認します。
reduxをインストール
動作確認用のプロジェクトを構築します。
mkdir test-redux
cd test-redux/
npm init
reduxをインストールします。
npm install --save redux
動作確認用ソースのフォルダ構造
今回、以下フォルダ構造で動作確認用の処理を実装します。
├── redux
│ ├── count
│ │ ├── count.actions.js
│ │ ├── count.reducer.js
│ │ └── count.types.js
│ ├── log
│ │ ├── log.actions.js
│ │ ├── log.reducer.js
│ │ └── log.types.js
│ ├── root-reducer.js
│ └── store.js
├── app.js
├── package-lock.json
└── package.json
Action Typesを定義
redux/count/count.types.js
const CountActionTypes = {
INCREMENT_COUNT: 'INCREMENT_COUNT',
DECREMENT_COUNT: 'DECREMENT_COUNT',
}
export default CountActionTypes
redux/log/log.types.js
const LogActionTypes = {
ADD_LOG: 'ADD_LOG',
DELETE_LOG: 'DELETE_LOG',
SET_LOG_LOADING: 'SET_LOG_LOADING',
}
export default LogActionTypes
Action Creatorsを定義
( actionオブジェクトを生成する関数 )
戻り値として actionオブジェクト(ユーザが行った操作を表現)
を返す関数を定義します。
{
type: "アクションの種類",
payload: "アクション実行に利用するデータ",
}
redux/count/count.actions.js
import CountActionTypes from './count.types'
export const incrementCount = () => {
return ({
type: CountActionTypes.INCREMENT_COUNT,
})
}
export const decrementCount = () => {
return ({
type: CountActionTypes.DECREMENT_COUNT,
})
}
redux/log/log.actions.js
import LogActionTypes from './log.types'
export const addLog = (id, text) => {
return ({
type: LogActionTypes.ADD_LOG,
payload: {
id,
text
},
})
}
export const deleteLog = id => {
return ({
type: LogActionTypes.DELETE_LOG,
payload: {
id
},
})
}
export const setLogLoading = () => {
return {
type: LogActionTypes.SET_LOG_LOADING,
}
}
Reducersを定義
( 状態を変更する純粋関数 )
引数として、stateオブジェクト(変化前の状態)
と actionオブジェクト
を受け取ります。
戻り値として、 stateオブジェクト(変化後の状態)
を返します。
redux/count/count.reducer.js
import CountActionTypes from './count.types'
const initialState = 0
const counterReducer = (state = initialState, action) => {
switch (action.type) {
case CountActionTypes.INCREMENT_COUNT:
return state + 1
case CountActionTypes.DECREMENT_COUNT:
return state - 1
default:
return state
}
}
export default counterReducer
redux/log/log.reducer.js
import LogActionTypes from './log.types'
const initialState = {
logs: [],
loading: false,
}
const logReducer = (state = initialState, action) => {
switch (action.type) {
case LogActionTypes.ADD_LOG:
return {
...state,
logs: [...state.logs, action.payload],
loading: false
}
case LogActionTypes.DELETE_LOG:
return {
...state,
logs: state.logs.filter(log => log.id !== action.payload.id),
loading: false
}
case LogActionTypes.SET_LOG_LOADING:
return {
...state,
loading: true
}
default:
return state
}
}
export default logReducer
Reducersを1つにまとめる
( redux.combineReducers )
redux/root-reducer.js
import { combineReducers } from 'redux'
import counterReducer from './count/count.reducer'
import logReducer from './log/log.reducer'
const rootReducer = combineReducers({
log: logReducer,
counter: counterReducer
})
export default rootReducer
Storeを生成
( redux.createStore )
redux/store.js
import { createStore } from 'redux'
import rootReducer from './root-reducer'
const initialState = {}
const store = createStore(rootReducer, initialState)
export default store
動作確認用処理
– 状態取得: store.getState()
– 状態更新: store.dispatch()
以下動作を確認するための処理を実装します。
store.getState()
で状態取得できることstore.dispatch(actionオブジェクト)
で状態更新できること
app.js
import { incrementCount, decrementCount } from './redux/count/count.actions'
import { addLog, deleteLog, setLogLoading } from './redux/log/log.actions'
import store from './redux/store'
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()))
実行
前準備|babel導入
import
などnodeでは利用できないので、babelを導入しておきます。
npm install --save-dev \
@babel/cli \
@babel/core \
@babel/node \
@babel/plugin-proposal-object-rest-spread \
@babel/preset-env
.babelrc
ファイルを作成して以下内容を記述します。
{
"presets": [
"@babel/preset-env"
],
"plugins": [
"@babel/plugin-proposal-object-rest-spread"
]
}
実行結果
$ npx babel-node 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}
補足|イメージ図
動作確認に利用した処理のイメージ図を描いてみました。