LoginSignup
103
83

More than 5 years have passed since last update.

redux-actionsを使って、reduxの記述で楽をしよう!

Last updated at Posted at 2017-05-23

今日は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の動きを把握できてない人にはわかりにくくなる
103
83
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
103
83