問題のコード
突然ですが以下の2つのコードのおかしな点、分かりますか?
const arr = [1, 10, 100]
const num: number = arr[100]
type ObjectLiteralLike = {
[lang: string]: string;
};
const butterfly: ObjectLiteralLike = {
en: "Butterfly",
fr: "Papillon",
it: "Farfalla",
};
const mariposa: string = butterfly.es
答え合わせ
それぞれ、arr[100]
とbutterfly.es
はundefined
にも関わらず、number
, string
として宣言されています。
しかし、これらの間違いは(デフォルト設定の)TypeScriptでは型宣言ミスとして検知してくれません。
console.log(num) // undefined
console.log(mariposa) // undefined
これは何が問題なの?
コンパイル時にエラーが出ないため、次に示すようなコードはに実行時にエラーが発覚することになります。
というか、一度TypeScriptの型と実際の状態に不整合が生じた時点で、それ以降のコードにおけるTypeScriptが持つ型安全性は機能しなくなります。
type ObjectLiteralLike = {
[lang: string]: string;
};
const butterfly: ObjectLiteralLike = {
en: "Butterfly",
fr: "Papillon",
it: "Farfalla",
};
const mariposa: string = butterfly.es
console.log(mariposa.toUpperCase()) // <- 実行時にエラー!!
ではどうするべきなのか?
このような、型チェックに引っかからないundefined
を防ぐための対策を2点挙げます。
1. noUncheckedIndexedAccess
を利用する
Typescriptのコンパイラオプションにはチェックの厳しさに関わる追加設定としてnoUncheckedIndexedAccess
があります。これを有効にすると、インデックス記法でアクセスした場合にundefined
型とのユニオン型として解釈されるようになります。
// Type 'number | undefined' is not assignable to type 'number'.
const num: number = arr[100]
// 正しい型宣言
const num: number | undefined = arr[100]
// Type 'string | undefined' is not assignable to type 'string'.
const mariposa: string = butterfly.es
// 正しい型宣言
const mariposa: string | undefined = butterfly.es
こうすることで、先ほど実行時に発生したエラーを事前に検知できます。
// 'mariposa' is possibly 'undefined'
console.log(mariposa.toUpperCase())
noUncheckedIndexedAccess
はfor-of
やarray.forEach()
には適用されず、これらはundefined
を考慮せず処理を継続できます。
const phoneticCodes: string[] = ["alpha", "bravo", "charlie"];
for (const p of phoneticCodes) {
// 通常通り動作
}
phoneticCodes.forEach((p: string) => {
// 通常通り動作
});
2. Mapped Typesを利用する
入力の形式が決まっているのであればMapped Typesを使用することで型の安全性を高められます。
// 予めキーの候補を列挙
type SystemSupportLanguage = "en" | "fr" | "it";
type ObjectLiteralLike = {
[key in SystemSupportLanguage]: string;
};
const butterfly: ObjectLiteralLike = {
en: "Butterfly",
fr: "Papillon",
it: "Farfalla",
};
// Property 'es' does not exist on type 'ObjectLiteralLike'
const mariposa: string = butterfly.es
まとめ
- TypeScriptのインデックスアクセスには
undefined
に関する型安全性の落とし穴がある -
noUncheckedIndexedAccess
やMapped Typesを利用して安全性を高めることができる
参考文献