6
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

問題のコード

突然ですが以下の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.esundefinedにも関わらず、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())

noUncheckedIndexedAccessfor-ofarray.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を利用して安全性を高めることができる

参考文献

プロを目指す人のためのTypeScript入門

6
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?