📝

型の達人になる!TypeScriptで使える11の型拡張テクニック

2024/09/29に公開

初めに

TypeScriptでは、型を利用して意図しない値の定義や宣言を防ぐことができます。
今回は、TypeScriptが提供している主な特殊な型拡張方法について説明します。

11の型定義

1. ユニオン型 (Union Types)

複数の型のいずれかを取る型です。|(パイプ)記号を使用します。

以下の場合、string型かnumber型を受け取ることができます。

// 公式
type "型Aと型Bのどちらかを持つ型" = "型A" | "型B"

// 例
type StringOrNumber = string | number;

const user : StringOrNumber = "Bob";

2. 交差型 (Intersection Types)

複数の型を組み合わせて、新しい型を作成します。

&(アンパサンド)記号を使用して型を定義します。

// 公式
type "型Aと型Bの両方を持つ型" = "型A" & "型B"

// 例
type Person = {
  name: string;
  age: number;
};

type Address = {
  city: string;
  country: string;
};

type PersonWithAddress = Person & Address;

const personWithAddress: PersonWithAddress = {
  name: '太郎',
  age: 30,
  city: '東京',
  country: '日本'
};

3. リテラル型 (Literal Types)

具体的な値を型として定義します。文字列リテラル型や数値リテラル型があります。

// 公式
type "具体的な値(値1 ~ 3)を持つ型" = "値1" | "値2" | "値3"

// 例
type Status = "pending" | "approved" | "rejected";

function updateStatus(status: Status) {
    console.log(`ステータスが更新されました: ${status}`);
}

updateStatus("approved"); // 正常に動作
updateStatus("completed"); // エラー: '"completed"' は 'Status' 型に割り当てられません。

4. タプル型 (Tuple Types)

固定長の配列で、各要素の型が異なる場合に使います。

// 公式
type "異なる要素を持つ型" = ["型A", "型B"];

// 例
type Pair = [string, number];
const example: Pair = ["apple", 5];

5. 型の除外型 (Exclusion<Types>)

ユニオン型から特定の型を除外します。Exclude を使います。

// 公式
type "型Aと型Bの両方に存在しない型" = Exclusion<"型A", "型B">;

// 例
type A = "a" | "b" | "c";
type B = "a" | "b";

type Excluded = Exclude<A, B>;  // "c"

6. 型の含む型 (Extract<Type, Union>)

ユニオン型の中で特定の型を抽出します。

// 公式
type "型Aと型Bの両方に存在する型" = Extract<"型A", "型B">;

// 例
type A = "a" | "b" | "c";
type B = "a" | "b";

type Extracted = Extract<A, B>;  // "a" | "b"

7. 型のオプショナル化(Partial<Type>)

型のすべてのプロパティをオプショナルにします。

// 公式
type "全てオプション化された型" = Partial<"オプション(任意)にしたい型">;

// 例
interface User {
  name: string;
  age: number;
  address?: string;
}

// Userインターフェースのすべてのプロパティをオプションにする
type PartialUser = Partial<User>;

const user: PartialUser = {
  name: 'Taro', // ageは必須要素だったが、無くても定義可能となっている
};

8. 型の必須化(Required<Type>)

型のすべてのプロパティを必須にします。

// 公式
type "全て必須要素とされた型" = Required<"必須要素にしたい型">;

// 例
interface User {
  name?: string;
  age?: number;
}
 
const user1: User = { age: 5 };
 
const user2: Required<User> = { age: 5 };
// Property 'name' is missing in type '{ age: number; }' but required in type 'Required<User>'
// nameはオプショナルとして指定していたが、必須項目となっている。

9. 指定プロパティの取り出し(Pick<Type, Keys>)

型から指定したプロパティだけを取り出します。

// 公式
type "基本の型から特定のキーのみ抽出された型" = Pick<"基本となる型", "取り出したいキー">;

// 例
interface Todo {
  title: string;       // 取り出す型として指定される
  description: string;
  completed: boolean;  // 取り出す型として指定される
}

// "title"と"completed"を取り出した型を定義
type TodoPreview = Pick<Todo, "title" | "completed">;

const todo: TodoPreview = {
  title: "Clean room",
  completed: false,
};

10. 指定プロパティの除外(Omit<Type, Keys>)

型から指定したプロパティを除外します。

// 公式
type "基本の型から特定のキーのみ除外された型" = Omit<"基本となる型", "除外したいキー">;

// 例
interface Todo {
  title: string;
  description: string; // 除外する型として指定される
  completed: boolean;
  createdAt: number;
}

// "description"を除外した型を定義
type TodoPreview = Omit<Todo, "description">;

const todo: TodoPreview = {
  title: "Clean room",
  completed: false,
  createdAt: 1615544252770,
}

11. オブジェクト型(Record<Keys, Type>)

任意のキーと値のペアを持つオブジェクト型を生成します。

// 公式
type "特定のキーと値の型で指定された型" = Record<"キーの型", "値の型">;

// 型
type CatName = "miffy" | "boris" | "mordred";
 
interface CatInfo {
  age: number;
  breed: string;
}
 
const cats: Record<CatName, CatInfo> = {
  miffy: { age: 10, breed: "Persian" },
  boris: { age: 5, breed: "Maine Coon" },
  mordred: { age: 16, breed: "British Shorthair" },
};

まとめ

レスポンス値が複数ある場合や複雑なデータを扱う場合、何度も型定義を行うのは手間がかかりますし、保守性やコードの可読性の観点からもあまり望ましくありません。そんな時、型定義をうまく活用することで、過度な型定義を避けつつ、保守性と可読性の高いコードを書くことができると今後のチームにとってもメリットをもたらすことができます。

また、記事を執筆している際に感じたのですが、型定義には集合的な考え方が根底にあると思います。各型がどのような集合なのか考慮して、ベストな型定義ができるよう精進したいものです。

参考文献

https://www.typescriptlang.org/docs/handbook/utility-types.html#partialtype

https://typescriptbook.jp/reference/values-types-variables

Discussion