『サバイバルTypeScriptの逆引き』の逆引きページ用のネタストック
このスクラップはTypeScript入門者向けウェブ書籍『サバイバルTypeScript』の逆引きTipsに追加しても良さそうなネタをストックするところです。
詳しい検証がなされていないものや正確性に乏しいものもあるかもしれないのでご注意ください。
オブジェクトの値から、そのキーのユニオン型を計算する
↓のようなオブジェクト定数ありきのコードで、
const status = {
ok: 200,
notFound: 404,
internalServerError: 500,
} as const;
ここから、
type StatusName = "ok" | "notFound" | "internalServerError";
のような型を計算で導き出したい。
keyof
とtypeof
でやる
やりかた: type StatusName = keyof typeof status;
オブジェクトの値から、そのプロパティの値のユニオン型を計算する
const status = {
ok: 200,
notFound: 404,
internalServerError: 500,
} as const;
こういう定数が定義されていて、ここから
type StatusCode = 200 | 404 | 500;
のようなnumberリテラル型のユニオン型を導き出したい。
typeof
+ keyof
+ インデックスシグネチャ
やりかた: type StatusCode = typeof status[keyof typeof status];
Javaプログラマ、PHPプログラマ向けにinstanceofを補足する。
union型の分岐漏れをコンパイラにチェックさせる方法
↓のようなユニオン型がコーディングされていて
type Feelings = "happy" | "sad";
それを絵文字にマッピングする関数があるとして↓
function toEmoji(feelings: Feelings): void {
switch (feelings) {
case "happy":
console.log("😄");
break;
case "sad":
console.log("😢");
break;
}
}
toEmoji('happy'); //=> 😄
toEmoji('sad'); //=> 😢
で、Feelings
に型を追加したとき、開発者がtoEmoji
の存在を知らないか、あまり気にしてなかった場合、どうなるか?
toEmoji
の挙動が未定義なものになってしまう。
// 改修後のコード
type Feelings = "happy" | "sad" | "not_so_good";
toEmoji('not_so_good'); // 何も出力されない……
これを、コンパイル時に気づけるようにする小技がある。
それが、網羅チェック(exhaustive checks)パターン。
function toEmoji(feelings: Feelings): void {
switch (feelings) {
case "happy":
console.log("😄");
break;
case "sad":
console.log("😢");
break;
default: // デフォルトケースを追加し
const exhaustiveCheck: never = feelings; // never型に変数をアサインするのがポイント!
}
}
この網羅チェックコードを追加した状態で、Feelings
を拡張すると……
コンパイラが問題のヒントをくれるようになる。
exhaustiveCheck
がunused variableになって気持ち悪い。もしくは、noUnusedLocals
コンパイルオプションで禁じ手になっている場合は↓のような例外を投げるアイディアもある。
class InvalidStateError extends Error {
constructor(value: never, message?: string) {
super(message);
}
}
type Feelings = "happy" | "sad" | "not_so_good";
function toEmoji(feelings: Feelings): void {
switch (feelings) {
case "happy":
console.log("😄");
break;
case "sad":
console.log("😢");
break;
default:
throw new InvalidStateError(feelings, `Unexpected state: ${feelings}`);
}
}
これなら、TypeScriptの仕様のために書いた謎の変数exhaustiveCheck
が存在する奇抜さもないし、JavaScript的な文脈でも「あ、用心深いコードだな」と思われるくらい。そして、コンパイルエラーを無視してしまうような大ミスをしても、実行時に例外になるので安心。
おまけ: eslintで網羅チェックする方法もある: typescript-eslint/switch-exhaustiveness-check.md at master · typescript-eslint/typescript-eslint
配列の値から、要素のユニオン型を導き出す
const currency = ['JPY', 'USD'] as const;
type Currency = typeof currency[number];
//=> "JPY" | "USD"
関連情報: TypeScript: 配列型からその要素のユニオン型を導出する方法。例えば、Array<A | B | C>からA | B | Cを導き出す。 - Qiita