前回 Redux入門 2日目 Reduxの基本・Actions
前回に引き続きTodoアプリの作成を通してReduxの要素の解説を行ないます。今回はReducerです。
2.2 Reducers
reducerは、actionを受けてstateを変更するの為のメソッドです
Designing the State Shap (Stateのデータ設計)
- Reduxでは、stateはすべて個別のオブジェクトとして保持されます
今回のTodoアプリでは2つの内容をstateで保持します
-
現在選択されている表示/非表示
-
todoのリスト
{
visibilityFilter: 'SHOW_ALL',
todos: [
{
text: 'Consider using Redux',
completed: true,
},
{
text: 'Keep all state in a single tree',
completed: false
}
]
}
stateにはUIの内容を入れないようにするのが推奨されています
Handling Actions (actionの制御)
reducerは、現在のstateとactionを受けて新しいstateを返すだけの純粋なメソッドです。
(previousState, action) => newState
reducerの中で以下のことをやってはいけません
- 引数のstate, actionインスタンスの値を変更する
- 副作用をおこす(APIを呼んだり、ルーティングを変えるなどなど)
- 毎回値が変わるもの(Date.now() や Math.random())を扱う
ここからReducerを書いていきます。
Reduxでは最初にreducerはstateがundefinedで呼び出します。その際に初期値を設定します。
import { VisibilityFilters } from './actions'
const initialState = {
visibilityFilter: VisibilityFilters.SHOW_ALL,
todos: []
}
function todoApp(state, action) {
if (typeof state === 'undefined') {
return initialState
}
// For now, don’t handle any actions
// and just return the state given to us.
return state
}
ES6ではもっと簡潔に記述できます
function todoApp(state = initialState, action) {
// For now, don’t handle any actions
// and just return the state given to us.
return state
}
actionがSET_VISIBILITY_FILTERの時の処理を追加します
function todoApp(state = initialState, action) {
switch (action.type) {
case SET_VISIBILITY_FILTER:
return Object.assign({}, state, {
visibilityFilter: action.filter
})
default:
return state
}
}
- Object.assignを使用してstateのコピーを作成しています。Object.assign(state, { visibilityFilter: action.filter })とすると、stateを変更することになってしまうのでやってはいけません。空のオブジェクトにマージさせないといけません。
※ Object.assignはES6の仕様ですがまだ実装されているブラウザは多くありません。polyfillかBabelを使用してください
- stateが不明の場合は、stateをそのまま返すのがセオリーです
reducerで制御するactionを追加します
function todoApp(state = initialState, action) {
switch (action.type) {
case SET_VISIBILITY_FILTER:
return Object.assign({}, state, {
visibilityFilter: action.filter
})
case ADD_TODO:
return Object.assign({}, state, {
todos: [
...state.todos,
{
text: action.text,
completed: false
}
]
})
case COMPLETE_TODO:
return Object.assign({}, state, {
todos: [
...state.todos.slice(0, action.index),
Object.assign({}, state.todos[action.index], {
completed: true
}),
...state.todos.slice(action.index + 1)
]
})
default:
return state
}
}
- COMPLETE_TODOでは、stateを変更しないようにsliceを使って順番を変更した新しい配列を作成しています。
Splitting Reducers(Reducersを分割)
reducer内でのvisiblity_filterとtodosの処理を子reducerとして分割すると分かりやすくなります。
初期値の値の処理はそれぞれの子reducerに記述します。
これはreducer compositionと呼ばれていて、Reduxアプリケーションの基本的な構成です。
function todos(state = [], action) {
switch (action.type) {
case ADD_TODO:
return [
...state,
{
text: action.text,
completed: false
}
]
case COMPLETE_TODO:
return [
...state.slice(0, action.index),
Object.assign({}, state[action.index], {
completed: true
}),
...state.slice(action.index + 1)
]
default:
return state
}
}
function visibilityFilter(state = SHOW_ALL, action) {
switch (action.type) {
case SET_VISIBILITY_FILTER:
return action.filter
default:
return state
}
}
function todoApp(state = {}, action) {
return {
visibilityFilter: visibilityFilter(state.visibilityFilter, action),
todos: todos(state.todos, action)
}
}
最後に、ReduxではcombineReducers()というユーティリティを提供しており、todoAppを書き換えることができます。combineReducerでは分割された子reducer名と同じキーのstateが使用されます。
import { combineReducers } from 'redux'
const todoApp = combineReducers({
visibilityFilter,
todos
})
export default todoApp