TypeScriptで Redux の Reducer部分を型安全かつスッキリ書く

はじめに

TypeScript 2.8がリリースされ色々な機能が追加されました。
その中でもConditional types、特にReturnTypeがなかなか優秀で
今まで諦めていた部分がきれいに書けるようになっております。

そこで個人的にずさんになりがちな、ReduxのReducer周りを少し改良してみたのでご紹介

コード

const INCREMENT = 'app/example/INCREMENT';
const SET_COUNT = 'app/example/SET_COUNT';

export const increment = () => ({
  type: INCREMENT as typeof INCREMENT,
});

export const setCount = (num: number) => ({
  type: SET_COUNT as typeof SET_COUNT,
  payload: {
    count: num,
  },
});

type Actions = (
  | ReturnType<typeof increment>
  | ReturnType<typeof setCount>
);

interface AppState {} // 本記事の趣旨に関係ないため割愛 
const initialState: AppState = {};

export default function reducer(
  state: AppState = initialState,
  action: Actions,
) {
  switch (action.type) {
    case INCREMENT:
      console.log(action.payload);
      // ↑ SET_COUNT以外でpayloadを参照するとエラーになる
      break;

    case SET_COUNT:
      console.log(action.payload);
      // ↑ OK
      break;

    default:
      const _: never = action;
      // ↑ ケースの定義もれがあった場合にエラーになる
  }
}

下で要点を解説していきます
また、今回は型をテーマにしていますので、
Reducerの処理自体は空っぽにしております

ReturnTypeの利用

TypeScript2.8の機能であるReturnTypeを利用することで、
ActionCreator関数の戻り値からActionの型を特定することができるようになりました。
こうすることでAction型定義の管理が少しだけ楽になります。

type Actions = (
  | ReturnType<typeof increment>
  | ReturnType<typeof setCount>
);

// ↓ こう解釈される

type Actions = {
    type: "app/example/INCREMENT";
} | {
    type: "app/example/SET_COUNT";
    payload: {
        count: number;
    };
}

ActionCreatorについて

普通にオブジェクトを返却しているかと思いきや
コードではtypeの値にas typeof XXXXを利用しています。

こうすることで関数の戻りであるaction.typestringではなく
その文字列そのものになります。

簡単な例↓

const HOGE = 'HOGE_STRING';
let hoge: typeof HOGE; // let hoge: 'HOGE_STRING'

少し野暮ったいですが、ここまでやることで型を縛ることができ、
Reducer部分型安全なActionを扱うことができるようになります。

defaultの部分

default部分のnever型への代入についてはTypeScript 2.0のneverでTagged union typesの絞込を漏れ無くチェックする の記事で紹介されている素敵なテクニックです。

最後に

TypeScriptではanyを多用すればエラーを黙らせることはできますが
いかにして型安全に設計・実装できるかを考えるのが楽しいですよね。

実はもっといい方法があるのではないかと
うずうずしていますので、いい案があればぜひ教えていただきたいです。

参考記事

色々参考にさせていただきました。

Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account log in.