23
10

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

TypeScript 2.4+におけるRedux Action

Posted at

TypeScriptでReduxのActionをどのように書くかは記事によってばらつきがあります。これからRedux+TypeScriptを書く人が迷わないようまとめたいと思います。

TL;DR

String enumsを使おう。

TypeScript2.4からEnums にString enumsが導入されています。2.4+以降の環境であればString enumsを使うことでより型安全にReduxが書けるよって記事です。

Enumsを使わないパターン

少し古い記事だと次のようにActionのtypeが文字列定数で定義されているパターンをみます。

const INCREMENT_COUNTER = 'INCREMENT_COUNTER';
const DECREMENT_COUNTER = 'DECREMENT_COUNTER';

JSの場合に近い書き方ですが、それぞれのActionを定義する際にせっかくのTypeScriptの型を生かしづらいです。型を生かしてActionを書くとすると次のようになりちょっと冗長ですね。

const INCREMENT_COUNTER: 'INCREMENT_COUNTER' = 'INCREMENT_COUNTER';

interface IncrementCounterAction extends Action {
  type: typeof INCREMENT_COUNTER;
  payload: {
    num: number;
  };
}
export const incrementCounter = (num: number): IncrementCounterAction => ({
  type: INCREMENT_COUNTER,
  payload: {
    num
  }
});

他にtypeは宣言せずに直接文字列リテラルを指定しているパターンもあります。

interface IncrementCounterAction extends Action {
  type: 'INCREMENT_COUNTER';
  payload: {
    num: number;
  };
}

Reducerでtypeをチェックする際に、ありえないcaseを書くとコンパイルエラーになる(TypeScript 2.1+であれば)ので文字列リテラルも悪くはないですが、文字列をコピペするのはあまりしたくないですし、名前空間を用意してスコープを絞りたくなります。

TypeScript 2.4以降での実装

Actionのtypeを定義する

String enumsを用いて次のように書きましょう。

enum ActionTypes {
  INCREMENT_COUNTER = 'INCREMENT_COUNTER',
  DECREMENT_COUNTER = 'DECREMENT_COUNTER'
}

Action/ActionCreatorを定義する

次にActionとActionCreatorを定義します。

import { Action } from 'redux';


interface IncrementCounterAction extends Action {
  type: ActionTypes.INCREMENT_COUNTER;
  payload: {
    num: number;
  };
}
export const incrementCounter = (num: number): IncrementCounterAction => ({
  type: ActionTypes.INCREMENT_COUNTER,
  payload: {
    num
  }
});

interface DecrementCounterAction extends Action {
  type: ActionTypes.DECREMENT_COUNTER;
  payload: {
    num: number;
  };
}
export const decrementCounter = (num: number): DecrementCounterAction => ({
  type: ActionTypes.DECREMENT_COUNTER,
  payload: {
    num
  }
});

export type CounterActions = IncrementCounterAction | DecrementCounterAction;

typeには上で定義したEnumsのメンバーを指定します。また、今回の本題ではないですが、ActionはFlux Standard Actionに従って定義することをお奨めします。

// 微妙な書き方
interface IncrementCounterAction extends Action {
  type: ActionTypes.INCREMENT_COUNTER;
  num: number;
}

Reducerを定義する

合わせてReducerも書くと次のようになります。

export interface CounterState {
  count: number;
}
const initialState: CounterState = {
  count: 0
};

const reducer = (state: CounterState = initialState, action: CounterActions): CounterState => {
  switch (action.type) {
    case ActionTypes.INCREMENT_COUNTER:
      return { count: state.count + action.payload.num };
    case ActionTypes.DECREMENT_COUNTER:
      return { count: state.count - action.payload.num };
    default:
      return state;
  }
};

export default reducer;

ありえないcase値を指定しようとするとコンパイラに怒られますし、Tagged union typesもちゃんと効くので各case内で存在しないpayloadにアクセスする心配もありません。ガシガシ補完きかせられるので書くのもちょっと楽になります。

まとめ

String enumsを使って型の恩恵をより得ながらReduxを書こう!

23
10
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
23
10

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?