機能としては知っていたものの使う機会がなかった Conditional Types を使ったのでメモ。

ElectronのMainプロセスでAPIを叩く際、API毎にchannelを用意するのは手間だと思い以下のようなIPC通信用の型を用意した。

同時にEnumも定義してMainプロセスとRendererプロセスで共有し、ReduxのActionとReducerと同じ要領で型安全にするのが目的。

export type Action<T, P = undefined> = {
  type: T;
  payload: P;
}

export enum ApiType {
  GET_ACCOUNT = 'GET_ACCOUNT',
  GET_TASKS = 'GET_TASKS',
}

export type ApiAction =
  | Action<ApiType.GET_ACCOUNT>
  | Action<ApiType.GET_TASKS, GetTasksRequest>;

しかし、上記の Action の定義では payload がないAPIを叩くときにも以下のように undefined を与えなければならない。

invokeApi({type: ApiType.GET_ACCOUNT, payload: undefined});

そこで、以下のように Actionpayload をConditional Typesで定義することにより、 P に型が与えられてないときは何もしないようにすることで呼び出し時にundefinedを与えなくてもいいようにした。

export type Action<T, P = undefined> = {
  type: T;
} & (P extends undefined ? unknown : { payload: P });
invokeApi({type: ApiType.GET_ACCOUNT});

ライブラリ等を作らないと自分で使うシーンはあまりないのかなぁと思っていたが、こういうテクニックを知っているときれいにコードが書けるものだなぁと思ったので記録しておく。