🚵‍♂️

【TS中級】#2 stringのユニオン×リテラル型で型安全な開発を【実務で頻出】

2024/08/17に公開

まえがき

今回はTypeScriptにおけるstring型のユニオン型とリテラル型について解説します。実務でTypeScriptを使っている方や、これから学ぼうとしている方のために、ユニオン型とリテラル型の基本を理解し、どのように効果的に使用できるかを掘り下げていきます。

今回のテーマ

今回のテーマは、TypeScriptにおけるstring型のユニオン型リテラル型の組み合わせについてです。これにより、コードの安全性を高め、型の誤用を防ぐ方法を学びます。

ユニオン型とは

ユニオン型とは、複数の型のいずれかを受け取ることができる型です。

type Status = string | number ;

上記の例では、Status型の変数は、文字列または数値のいずれかを持つことができます。このように、ユニオン型は複数の型を組み合わせた型定義できます。

リテラル型とは

リテラル型は、具体的な値そのものを型として定義するものです。

type One = 1;

const x: One = 1;
const y: One = 2; // Error: Type '2' is not assignable to type '1'.

この例では、Oneという型を割り当てられた変数は、数値の1のみを持つことができるので、最後の行はエラーになります。

ユニオン型とリテラル型の組み合わせ

これらを実際の例で見てみましょう。例えば、ブログのカテゴリを定義する際に、それぞれのカテゴリに対応する英語と日本語の表示名を持つオブジェクトを定義したい場合です(実際の僕の個人開発のコードです)。

export type Category = "Tech" | "DevDiary" | "LifeStyle";

export const category: { [key in Category]: { en: string; ja: string } } = {
  Tech: { en: "Tech Articles", ja: "技術記事" },
  DevDiary: { en: "Dev Diary", ja: "開発日記" },
  LifeStyle: { en: "Lifestyle & Hobby", ja: "雑談" },
};

コードの解説

  1. Categoryの定義:
    Category型は、"Tech", "DevDiary", "LifeStyle"の3つの文字列リテラルのユニオン型です。この型を使用することで、これらの3つの値以外がCategoryとして扱われることを防ぎます。

  2. categoryオブジェクトの定義:
    categoryオブジェクトは、キーとしてCategory型を使用し、その値として英語(en)と日本語(ja)の表示名を持つオブジェクトを持っています。ここで、key in Categoryという記法を用いることで、categoryオブジェクトのキーがCategory型であることを保証しています。タイポや勘違いでCategory型でないものをオブジェクトに追加しようとするとエラーが出てくれることが重要です。

より踏み込んだ話

上記のコードにおいて、Category型に新しい文字列"other"が追加されたら、どこかでエラーが出るでしょうか?
実は、categoryオブジェクトに "other"をキーにもつプロパティがないという旨のエラーが出ます。categoryオブジェクトの型定義から、オブジェクトのキーはCategory型のキーを過不足なく持つことを期待しているので、Category型を増やした際に、自動でエラーを吐いてくれます。

ただ、エラーが出るのではなく、常にcategoryのキーとCategory型が一致するようにコーディングできれば一番良いと思う方もいるかもしれません。

これは、categoryを手打ちで定義し、そのキーをすべて取得し、それらをユニオン×リテラル型でもつCategory型を自動で定義するという実装です。

以下がその実装になります。

export const category = {
  Tech: { en: "Tech Articles", ja: "技術記事" },
  DevDiary: { en: "Dev Diary", ja: "開発日記" },
  LifeStyle: { en: "Lifestyle & Hobby", ja: "雑談" },
};

type Category = keyof typeof category;

categoryの型をtype ofで取得し、その中のキーをkey ofで取得することで、今までと同様のCategory型を定義するのと同時に、型の追加や削除がcategoryの変更に依存するようになり変更しやすくなりました。

ちなみに僕の感覚だと定義の向きが逆に感じてしまう(大体のコーダーは 型定義→オブジェクト定義 という感覚を持っている...はず)のでこの実装は行なっていません(大規模開発でヒューマンエラーが出る可能性が高いなどの理由があるならばこの書き方をするべき)。
さらに言うと、categoryのvalueの方の型がうまく定義できない(やり方わかる方いればご教授お願いします🙇)ので今回は使いません。

(category: Record<string, { en: string; ja: string }>;のように定義すると、Category型がstringになってしまう)

終わりに

今回は、TypeScriptにおけるstring型のユニオン型とリテラル型について解説しました。これらを活用することで、コードの信頼性を高め、型安全な開発を行うことができます。特に、ユニオン型とリテラル型の組み合わせは、特定の値のセットを扱う際に非常に役立ち、実務でもよく見かけるので覚えておいて損は無いと思います。
お読みいただきありがとうございました🥳

Discussion