今日はredux-actions(記載時点ではv2.0.1)という公式推奨パッケージを使って、
reduxのactionとreducerを楽にスッキリ書く方法を紹介します。
追記:現在は公式のドキュメントができているので、こちらを参照するとより確実です。
https://redux-actions.js.org/docs/api/index.html
アクションの形式
reducerを書く際に下記の形式(※1)を知っている必要があります。
{
type : {symbol|string}, // アクションタイプ
payload : {any}, // メインの情報
error : {boolean}, // エラーかどうか
meta : {any}, // payloadに乗らなかった情報
}
アクション(クリエイター)の生成
まとめて楽チンcreateActionsを使います。
キー名にアクションタイプを指定し、アクションはそれのlowerCamelで作られます。
import { createActions } from 'redux-actions';
import types from './actionTypes';
export default createActions(
// payloadを整形したい場合、またはmetaを利用したい場合は
// 第一引数にオブジェクトでその定義をする。
{
// payloadだけを使う場合
// アクションの引数を元に、どんな形でpayloadを作るか定義します。
// この形式の場合、metaは作られません。
[types.GET_HOGE] : (...args) => { test1 : args[0], test2 : args[1] },
// metaも使う場合
// 値に配列を指定し、1つ目がpayload, 2つめがmetaの定義です。
[types.GET_FUGA] : [
(...args) => args[0],
(...args) => args[1],
],
},
// その他シンプルな場合
// payloadはアクション実行時の第一引数をそのままわたし、
// metaは使わない時は、
// 第二引数以降にタイプだけ渡していきます。
types.GET_PIYO,
types.GET_PUNI,
);
同じアクションを手書きすると下記になります。だいぶ楽できてますね!
// before =================================================
export default {
getHoge : (arg0, arg1) => {
type : types.GET_HOGE,
payload : {
test1 : arg0,
test2 : arg1,
},
},
getFuga : (arg0, arg1) => {
type : types.GET_FUGA,
payload : arg0,
meta : arg1,
},
getPiyo : (arg) => {
type : types.GET_PIYO,
payload : arg,
},
getPuni : (arg) => {
type : types.GET_PUNI,
payload : arg,
},
};
// after =====================================================
export default createActions(
{
[types.GET_HOGE] : (...args) => { test1 : args[0], test2 : args[1] },
[types.GET_FUGA] : [
(...args) => args[0],
(...args) => args[1],
],
},
types.GET_PIYO,
types.GET_PUNI,
);
もしアクションタイプ名とアクション名を変えたい場合は、createActionで個別に作ると変えられます。
第一引数がアクションタイプ、第二がpayloadの設定関数、第三がmetaの設定関数になります。
// createActionsだと "getHoge" となるところを "get" にしたい時
export default {
get : createAction(
types.GET_HOGE,
(...args) => { test1 : args[0], test2 : args[1] },
),
};
reducerの生成
handleActionsでこちらも楽チン生成します。
import actions from './action';
const initialState = {
test1 : '',
test2 : '',
test3 : '',
piyo : '',
puni : '',
};
// 第一引数にはreducerの設定を入れたオブジェクトを、
// 第二引数には初期stateオブジェクトを渡します。
export default handleActions({
// キーについて
// switchで書く場合のアクションタイプをオブジェクトのキーとしますが、
// ここではcreateActionsで作られたアクション自体を [] で囲んでいます。
// オブジェクトのキーに[]で囲んだ変数を入れると、文字列と判定され、
// toString() が呼ばれます。
// createActionsで作られたアクションはtoStringでアクションタイプを返すので、
// このように「このアクションが発行されたら」と、直感的な書き方にすることができます。
// バリューについて
// バリューには関数を作成します。
// 引数は通常のreducerと同様、現状のstateと、actionが渡されます。
// actionは ※1 の形式で渡されます。
[actions.getHoge] : (state, action) => ({
...state,
test1 : action.payload.test1,
test2 : action.payload.test2,
}),
[actions.getFuga] : (state, actions) => ({
...state,
test3 : (actions.meta === 'FUGA') ? action.payload : '',
}),
[actions.getPiyo] : (state, action) => ({
...state,
piyo : action.payload,
}),
[actions.getPuni] : (state, action) => ({
...state,
puni : action.payload,
}),
}, initialState);
同じreducerをswitchで書く下記のようになります。
幾分かスッキリした印象です。
immutable.jsを使うとよりスッキリしますが、話がそれるのでここでは割愛します。
また、actionTypeをreducer側で呼ばないことで、actionCreator側だけで使うことになり、
影響範囲を狭めることができています。
// before ==================================================
import types from './actionTypes';
... initialState省略 ...
export default (state = initialState, action) {
switch(action.type) {
case types.GET_HOGE:
return Object.assing({}, state, {
test1 : action.payload.test1,
test2 : action.payload.test2,
});
case types.GET_FUGA:
return Object.assign({}, state, {
test3 : (actions.meta === 'FUGA') ? action.payload : '',
});
case types.GET_PIYO:
return Object.assing({}, state, {
piyo : action.payload,
});
case types.GET_PUNI:
return Object.assing({}, state, {
puni : action.payload,
});
default:
return state;
}
}
// after ===================================================
import actions from './action';
... initialState省略 ...
export default handleActions({
[actions.getHoge] : (state, action) => ({
...state,
test1 : action.payload.test1,
test2 : action.payload.test2,
}),
[actions.getFuga] : (state, actions) => ({
...state,
test3 : (actions.meta === 'FUGA') ? action.payload : '',
}),
[actions.getPiyo] : (state, action) => ({
...state,
piyo : action.payload,
}),
[actions.getPuni] : (state, action) => ({
...state,
puni : action.payload,
}),
}, initialState);
余談ですが、こちらのhandleActionsを知らない頃に、自前でreducerをすっきりさせる記事を書きました。
内部的には似た形なので、よければこちらもご覧ください。
Reduxのreducerはオブジェクトで書こーず!!
その他の機能
公式では combineActions と、ミドルウェアと組み合わせる場合の説明があります。
気になる方は読んでみてください。
npm redux-actions
まとめ
- redux-actions の機能を使って楽をする方法を紹介
- 特に createActions, handleActions の使い方と、利用前後の差分を紹介
- メリット
- 楽をできる、記述がスッキリする
- デメリット
- 追加の仕組みを知る必要がある、元々のreduxの動きを把握できてない人にはわかりにくくなる