Skip to content

高度な型機能

公開日:November 16, 2024更新日:November 28, 2024
JavaScriptTypeScriptCoding📄

TypeScriptには、より柔軟で強力な型の表現を可能にする高度な型機能が用意されています。このセクションでは、インデックスシグネチャ、keyofと型の操作、条件型(Conditional Types)、そしてマップ型(Mapped Types)について説明します。

インデックスシグネチャ

インデックスシグネチャを使うことで、オブジェクトが複数のプロパティを持ち、それらのプロパティが特定の型を持つことを定義できます。主に動的にプロパティを扱う場合に使用されます。

typescript
interface StringArray {
  [index: number]: string;
}

let myArray: StringArray;
myArray = ["Alice", "Bob", "Charlie"];

console.log(myArray[1]); // Bob

上記の例では、StringArrayインターフェースは任意の数値インデックスを持つことができ、それぞれの値がstring型であることを定義しています。これにより、配列のような構造を型で表現することができます。

typescript
interface Dictionary {
  [key: string]: number;
}

let scores: Dictionary = {
  alice: 90,
  bob: 85,
  charlie: 95,
};

console.log(scores["alice"]); // 90

この例では、Dictionaryインターフェースを使って、任意の文字列キーを持つオブジェクトを定義し、その値がnumber型であることを示しています。

keyofと型の操作

keyof演算子を使うと、オブジェクト型のすべてのキーを文字列リテラル型として取得できます。これにより、動的にオブジェクトのキーを操作することが可能になります。

typescript
interface Person {
  name: string;
  age: number;
  isAdmin: boolean;
}

type PersonKeys = keyof Person; // "name" | "age" | "isAdmin"

function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key];
}

let person: Person = { name: "Alice", age: 30, isAdmin: true };

let name = getProperty(person, "name"); // OK, name は string 型
let age = getProperty(person, "age");   // OK, age は number 型
// let invalid = getProperty(person, "address"); // エラー: 'address' は 'Person' のキーではありません

keyofを使うことで、オブジェクトのキーを動的に扱う関数を定義し、型安全にプロパティを参照することができます。

Conditional Types(条件型)

条件型は、extendsキーワードを使って型を条件によって変化させることができる強力な機能です。条件に応じて異なる型を返すことで、柔軟な型定義を可能にします。

typescript
type IsString<T> = T extends string ? "string" : "not string";

type Test1 = IsString<string>;  // "string"
type Test2 = IsString<number>;  // "not string"

上記の例では、IsStringという型を定義し、Tstring型であれば"string"というリテラル型を返し、それ以外の場合は"not string"を返します。これにより、型を条件によって動的に変更することができます。

また、条件型を使うことで型のフィルタリングも可能です。

typescript
type Exclude<T, U> = T extends U ? never : T;

type NonString = Exclude<"a" | "b" | 1 | 2, string>; // 1 | 2

この例では、Excludeを使って、TのうちUに一致する型を除外しています。これにより、指定した型を取り除いた新しい型を生成することができます。

Mapped Types(マップ型)

マップ型を使うことで、既存の型から新しい型を作成することができます。マップ型は、既存のプロパティの型を変更したり、すべてのプロパティに対して同じ処理を適用する場合に使用します。

typescript
interface Person {
  name: string;
  age: number;
}

type ReadonlyPerson = {
  readonly [K in keyof Person]: Person[K];
};

let person: ReadonlyPerson = {
  name: "Alice",
  age: 30,
};

// person.age = 31; // エラー: 'age' は読み取り専用です

上記の例では、ReadonlyPersonという型を作成しています。この型はPerson型のすべてのプロパティにreadonly修飾子を追加しています。これにより、オブジェクトのプロパティを読み取り専用にすることができます。

さらに、プロパティの型を変更することも可能です。

typescript
type Optional<T> = {
  [K in keyof T]?: T[K];
};

type OptionalPerson = Optional<Person>;

let optionalPerson: OptionalPerson = {
  name: "Bob",
};

この例では、Optionalというマップ型を定義し、すべてのプロパティをオプショナルにしています。これにより、Person型のプロパティがすべて省略可能となったOptionalPerson型を作成しています。

まとめ

高度な型機能を使うことで、TypeScriptの型システムをより強力に活用することができます。インデックスシグネチャを使うことで動的なプロパティを持つオブジェクトを定義し、keyofや条件型を使って型を柔軟に操作できます。さらに、マップ型を使うことで既存の型を再利用しながら新しい型を簡単に作成することができます。これらの高度な型機能を駆使することで、より安全で表現力の高いコードを書くことができるようになるでしょう。