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

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

January 21, 2019

3 min read

mitsuruogMitsuru Ogawa

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

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 の制限。 勉強になりました。