型の達人になる!TypeScriptで使える11の型拡張テクニック
初めに
TypeScriptでは、型を利用して意図しない値の定義や宣言を防ぐことができます。このことにより、コードの可読性や保守性の向上に繋げることが可能になります。一方で、複雑なデータの型定義を繰り返し行うと、コードが冗長になったり複雑化することで、逆に保守性が低下する恐れがあります。重複する型定義を避けるため、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; // 取り出す型として指定される
}
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;
}
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" },
};
まとめ
レスポンス値が複数ある場合や複雑なデータを扱う場合、何度も型定義を行うのは手間がかかりますし、保守性やコードの可読性の観点からもあまり望ましくありません。そんな時、型定義をうまく活用することで、過度な型定義を避けつつ、保守性と可読性の高いコードを書くことができると今後のチームにとってもメリットをもたらすことができます。
また、記事を執筆している際に感じたのですが、型定義には集合的な考え方が根底にあると思います。各型がどのような集合なのか考慮して、ベストな型定義ができるよう精進したいものです。
参考文献
Discussion