1. 概要
react+reduxで大規模SPAを構築する時に、画面ごとに定義されているモデルデータをそれぞれのAction
で更新しなければならいので、よく似てるモデルデータのやり取りするソースコードをあっちこっちにコピーするのが効率よくない一歩、メンテナンスし難くなるため、成る可くそれを避けたい。
本文はreducerやの共通化のアプローチでそれの解決を目指す。
2. なぜ共通action/reducerが必要なのか?
- 共通的なUIコンポーネントが複数画面で使われる、あるいは同じ画面で複数回使われる場合、表示用のデータもそれぞれのモデルに持つ必要がある。
- 該当データの更新処理をそれぞれのaction/reduxを定義するのが、面倒だし、
DRY
に違反する。
例:都道府県・市区町村入力の共通UIコンポーネントLocationSuggest
を使っている場合、全く一緒なソースコードが生まれる
タッチすると都道府県・市区町村のリストが表示され、選択できる。
-
UIコンポーネント
LocationSuggest.jsx
-
モデルデータ
LocationSuggestRecord.js
-
画面
PageA
でLocationSuggest
を使っている
// actionA.js
hoge(value) { disptach({type: 'PAGE_A_CHANGE_WORK_ADDRESS_HOGE', value}) }
fuga(value) { disptach({type: 'PAGE_A_CHANGE_WORK_ADDRESS_FUGA', value}) }
// dataA.js
DataA {
workAddress : new LocationSuggestRecord(),
}
// reducerA.js
function(state, action) {
switch(action.type) {
case 'PAGE_A_CHANGE_WORK_ADDRESS_HOGE' :
return state.setIn['workAddress', 'Hoge'], value);
case 'PAGE_A_CHANGE_WORK_ADDRESS_FUGA' :
return state.setIn['workAddress', 'Fuga'], value);
}
}
- 画面
PageB
もLocationSuggest`を使っている
// actionB.js
hoge(value) { disptach({type: 'PAGE_B_CHANGE_WORK_ADDRESS_HOGE', value}) }
fuga(value) { disptach({type: 'PAGE_B_CHANGE_WORK_ADDRESS_FUGA', value}) }
// dataB.js
DataB {
workAddress : new LocationSuggestRecord(),
}
// reducerB.js
function(state, action) {
switch(action.type) {
case 'PAGE_B_CHANGE_WORK_ADDRESS_HOGE' :
return state.setIn['workAddress', 'Hoge'], value);
case 'PAGE_B_CHANGE_WORK_ADDRESS_FUGA' :
return state.setIn['workAddress', 'Fuga'], value);
}
}
3. action/reducerを共通化する
共通化するとこうなる。
- アクション
locationSuggestAction.js
// locationSuggestAction.js
hoge(type, value) { disptach({type, commonType: 'HOGE', value}) }
fuga(type, value) { disptach({type, commonType: 'FUGA', value}) }
- Reducer
locationSuggestReducer.js
// locationSuggestReducer.js
function(state, action) {
switch(action.commonType) {
case 'HOGE' :
return state.setHoge(action.value)
case 'FUGA' :
return state.setFuga(action.value)
}
}
- 画面
PageA
でcommonType
指定で共通アクションlocationSuggestAction
を呼び出す
locationSuggestAction.hoge('PAGE_A_CHANGE_WORK_ADDRESS', 'value');
- 画面
PageA
のreducerA
でcommonType
を判断してからで共通recuderlocationSuggestReducer
を呼び出す
// reducerA.js
function(state, action) {
switch(action.type) {
case 'PAGE_A_CHANGE_WORK_ADDRESS' :
// 共通reducerを呼び出す
return state.set('workAddress', locationSuggestReducer(state.get('workAddress'), action));
case yy :
return zz
}
}
- 画面
PageB
も同様
locationSuggestAction.hoge('PAGE_B_CHANGE_WORK_ADDRESS', 'value');
- 画面
PageB
のreducerB
も同様
// reducerB.js
function(state, action) {
switch(action.type) {
case 'PAGE_B_CHANGE_WORK_ADDRESS' :
// 共通reducerを呼び出す
return state.set('workAddress', locationSuggestReducer(state.get('workAddress'), action));
case yy :
return zz
}
}
共通action/reducer特徴の纏め
- typeを引数として呼び出す時に渡すだけ、動的にアクション関数を生成はしないので、無駄なレンダリングを防げる
- 今までのアクションとreducer定義のやり方に従っているので、違和感がない