redux

Redux入門 3日目 Reduxの基本・Reducers(公式ドキュメント和訳)

前回 Redux入門 2日目 Reduxの基本・Actions

前回に引き続きTodoアプリの作成を通してReduxの要素の解説を行ないます。今回はReducerです。


2.2 Reducers

reducerは、actionを受けてstateを変更するの為のメソッドです


Designing the State Shap (Stateのデータ設計)


  • Reduxでは、stateはすべて個別のオブジェクトとして保持されます

今回のTodoアプリでは2つの内容をstateで保持します


  1. 現在選択されている表示/非表示


  2. 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

続き Redux入門 4日目 Reduxの基本・Stores