Apr 1, 2022
6 mins read
Twitterを眺めていると汎用性が高そうな関数を作成できるTypeScriptのツイートがあったので、整理するためにまとめる。
TypeScript Tip #15
関数の引数のタイプをみて、タイプに応じてpayloadが必要か必要がないかをTypeScriptで判断する。
// 第一引数がSIGN_OUTの場合は、payloadはなし
sendEvent('SIGN_OUT')
// 第一引数がLOG_INの場合は、payloadは必須
sendEvent("LOG_IN", {
userId: '123'
})
成功パターン
sendEvent('SIGN_OUT')
sendEvent("LOG_IN", {
userId: '123'
})
失敗パターン(コンパイルエラー)
sendEvent('SIGN_OUT', {})
sendEvent('LOG_IN', {
userId: 122
})
sendEvent('LOG_IN', {})
sendEvent('LOG_IN')
以下のコードで実現可能
type AuthEvent =
| {
type: 'LOG_IN'
payload: {
userId: string
}
}
| {
type: 'SIGN_OUT'
}
const sendEvent = <Type extends AuthEvent['type']>(
...args: Extract<AuthEvent, { type: Type }> extends { payload: infer TPayload }
? [Type, TPayload]
: [Type]
) => {
const [type, payload] = args
// ...
}
Type
の型を作成する。Type
はLOG_IN
orSIGN_OUT
のどちらかになる。AuthEvent['type']
のようにキーを指定して型定義を取得する方法をルックアップ型という。<Type extends AuthEvent['type']>
...args
は引数を配列で格納する...args
// Ex) ['SIGN_OUT'], ['LOG_IN', { userId: '123' }]
Extract
型でAuthEvent
が{ type: Type }
に代入可能な型を取り除くことができる。Extract<AuthEvent, { type: Type }>
1の時点でType
はLOG_IN
orSIGN_OUT
にどちらかに決まっていたので、{ type: Type }
は{ type: 'LOG_IN' }
or { type: 'SIGN_OUT' }
のどちらかになる。
なので、上記のコードで以下のどちらかになる
{
type: 'LOG_IN'
payload: { userId: string }
}
or
{
type: 'SIGN_OUT'
}
Extract
型については、以下の記事を参考にするとわかりやすいかも。
【TypeScript】Utility Typesをまとめて理解する
Extract
の型はわかったので{ payload: infer TPayload }
をが存在するかの条件分岐を行う。Extract
型にpayload
が存在するかを確認して、[Type, TPayload]
or[Type]
の分岐を行う。infer
は型はpayload
が存在していればTPayload
として推論してくれる。
この時点でpayload
がある引数なのか、ない引数なのかの判定ができる。Extract<AuthEvent, { type: Type }> extends { payload: infer TPayload }
? [Type, TPayload]
: [Type]
infer
については以下の記事がしっくりきた。
TypeScriptのinferを今度こそちゃんと理解する
ちなみに失敗パターンは以下の型エラーになる
sendEvent('SIGN_OUT', {})
// Expected 1 arguments, but got 2.
sendEvent('LOG_IN', {
userId: 122
// Type 'number' is not assignable to type 'string'
})
sendEvent('LOG_IN', {})
// Argument of type '{}' is not assignable to parameter of type '{ userId: string; }'.
// Property 'userId' is missing in type '{}' but required in type '{ userId: string; }'.
sendEvent('LOG_IN')
// Expected 2 arguments, but got 1.
うまく説明できていない気がするが、TypeScriptの復習になった。今後も続けていきたい。
Thank you Matt Pocock for your informative tweet. I learned a lot.
TypeScript Tip #15
【TypeScript】Utility Typesをまとめて理解する
TypeScriptのinferを今度こそちゃんと理解する