はじめに
combineReducers
大好きです。Stateの形と直結した直感的なやり方でReducerを分割できてすごくわかりやすいです。
ただ別のStoreじゃないかってレベルで外側のStateにアクセス出来ないので、そういう時は巨大なReducerを作るはめになることがあります。
巨大なReducer
例えば、これはいまのところ巨大なReducerではありませんが、将来的にそうなりうるState構造とそのReducerの例です。
ここで先に言っておくと、この例はActionをDispatchする側がcurrentTodoKey
を取得していないという状況があまり想像できないので、もしかするとあまり良い例ではないのかもしれません。
// State
{
todos: {
todoA: [],
todoB: [],
todoC: []
},
currentTodoKey: "todoA"
}
// Reducer
const reducer = (state = { todos: {}, currentTodoKey: "default" }, action) => {
const addTodo = (state, key, todo) {
return Object.assign({}, state, {
todos: Object.assign({}, state.todos, {
[key]: (state.todos[key] || []).concat(todo)
})
})
}
switch (action.type) {
case 'ADD_TODO':
var { key, todo } = action.payload
return addTodo(state, key, todo)
case 'ADD_TODO_TO_CURRENT_KEY':
var { todo } = action.payload
return addTodo(state, state.currentTodoKey, todo)
case 'UPDATE_CURRENT_KEY':
var { newKey } = action.payload
return Object.assign({}, state, {
currentTodoKey: newKey
})
default:
return state
}
}
要するにこの例では何が言いたいのかというと、combineReducers
を使ってこのReducerを2つのReducertodos
とcurrentTodoKey
に分けたいが、todos
のReducerが外部のcurrentTodoKey
を必要とするためにそれが不可能であるということです。
そして、この記事の目的はこれをcombineReducersライクな方法で分けることです。
Section Reducerとは
ここで、僕が勝手にSection Reducer
と読んでいるものを使って分割を進めていきます。
Section ReducerはReducerの
・(previousState, action) => nextState
このような定義に対して、以下の様なものになります。
・(previousSectionState, action, state) => nextSectionState
ここで使われているprevious/nextSectionState
はこの例で言うtodos
, currentTodoKey
に相当するものです。
またcombineReducers
のやり方に合わせて、以下のように表すこともあります。
・(previousState, action, entireState) => nextState
注意して欲しいのはSection Reducer
は巨大なReducerを小さな単位に分けるためのただの道具であって、それ以外の何者でもないということです。
Section Reducerの適用
それでは、早速先程の例にSection Reducer
のやり方を適用してみます。
const todos = (state = {}, action, entireState) => {
switch (action.type) {
case 'ADD_TODO':
var { key, todo } = action.payload
return Object.assign({}, state, {
[key]: (state[key] || []).concat(todo)
})
case 'ADD_TODO_TO_CURRENT_KEY':
var { todo } = action.payload
var key = entireState.currentTodoKey
return Object.assign({}, state, {
[key]: (state[key] || []).concat(todo)
})
default:
return state
}
}
const currentTodoKey = (state = "default", action) => {
switch (action.type) {
case 'UPDATE_CURRENT_KEY':
var { newKey } = action.payload
return newKey
default:
return state
}
}
const reducer = (state, action) => ({
todos: todos(state.todos, action, state),
currentTodoKey: currentTodoKey(state.currentTodoKey, action)
})
無事に分割することに成功しました。
combineSectionReducersを使う。
上の例で示したこの部分ですが毎回書くのは少し面倒くさそうです。
const reducer = (state, action) => ({
todos: todos(state.todos, action, state),
currentTodoKey: currentTodoKey(state.currentTodoKey, action)
})
またcombineReducers
がしてくれるように、それぞれのReducerが正しいReducerかどうか確かめたい時もあるかもしれません。
そこで、私がnpmに公開しているcombineSectionReducers
というパッケージを利用して、以下のように書き換えます。
import combineSectionReducers from 'combine-section-reducers'
const reducer = combineSectionReducers({
todo, currentTodoKey
})
これで、この記事の目標は達成できました。
combineSectionReducersについて
もちろんcombineSectionReducers
はcombineReducers
のように深くネストして使うことができます。
またcombineReducers
によって作られたState木の一部分にだけcombineSectionReducers
を適用するといったこともできます。
それはcombineSectionReducers
の返す関数がただのReducerとしてもSection Reducer
としても使うことができるようになっているためです。
さいごに
combineReducers大好きです。
あと、この記事に載せたコードの動作確認は行っていません。
リンク
NPM:
GitHub: ryo33/combine-section-reducers