typesafe-actionsのActionTypeに動的な文字列を使ってはいけない
2019-01-22

typesafe-actionsのActionTypeに動的な文字列を使ってはいけない

(完全に自分用のメモです。)

TypeScript+React+Reduxを使うときに気に入って使っているtypesafe-actions
意外なところでハマりポイントがありました。

ReducerでActionの型情報が見れない

よくあるReducer。

export const weatherReducer = (state: WeatherState = initialState, action: Action): WeatherState => {

  switch (action.type) {

    case getType(actions.weatherSetAction):
      return Object.assign({}, state, { weather: new Weather(action.payload) });

    case getType(actions.weatherErrorAction):
      console.error(action.payload.message);
      return state;

    default:
      return state;
  }
};

ところがaction.payloadの型情報が正しく取れていません。なぜだろう。。。

エラーの内容はこちら。

TS2339: 
Property 'payload' does not exist on type 
'{ type: string; payload: { lat: number; lng: number; }; } | 
 { type: string; payload: Response; } | 
 { type: "@@weather/ERROR"; payload: Error; } | 
 { type: "@@map/READY"; }'. 
  Property 'payload' does not exist on type '{ type: "@@map/READY"; }'.

Actionの型定義は一見良さそうなので、最初は何が原因かわかりませんでした。

(追記: 2019/01/23)
というか型定義良くない。type: stringになっている。Blog用にエラーを整形したら気づいた。エラーメッセージもっと冷静に読まないと。

ActionTypeに動的な文字列を使ってはいけない

原因はこれでした。ActionTypeをPREFIXを使ったテンプレート文字列で定義している部分。

// constants.ts
const PREFIX = "@MyApp"
export const MAP_READY   = `${PREFIX}/READY`;
export const WEATHER_GET = `${PREFIX}/GET`;
export const WEATHER_SET = `${PREFIX}/SET`;
export const WEATHER_ERROR = `${PREFIX}/ERROR`;

公式ドキュメントにも書いてありました。

PRO-TIP: string constants limitation in TypeScript

TypeScriptには文字列の定義に関する制限事項があってだね。テンプレート文字列などで動的に文字列を組み立てた場合、型情報が全部stringになってしまって、reducerのcaseの中の型情報が壊れるよ。(超訳)

// Example file: './constants.ts'

// WARNING: Incorrect usage
export const ADD = prefix + 'ADD'; // => string
export const ADD = `${prefix}/ADD`; // => string
export default {
   ADD: '@prefix/ADD', // => string
}

// Correct usage
export const ADD = '@prefix/ADD'; // => '@prefix/ADD'
export const TOGGLE = '@prefix/TOGGLE'; // => '@prefix/TOGGLE'

(公式ドキュメントから抜粋)

という訳で、正しいやり方は普通に文字列だけでActionType定義すればいいだけでした。

// constants.ts
export const MAP_READY   = "@MyApp/READY";
export const WEATHER_GET = "@MyApp/GET";
export const WEATHER_SET = "@MyApp/SET";
export const WEATHER_ERROR = "@MyApp/ERROR";

なるほど、TypeScriptの制限。
勉強になりました。