TypeScript 型の深掘り
TypeScript の extends keyof
と in keyof
は、型システムにおいてオブジェクトのキーを扱うための重要な概念です。これらを使うことで、より厳密な型定義が可能になります。
keyof
演算子
まず、keyof
演算子について説明します。これは、ある型 T
のすべてのプロパティ名(キー)のユニオン型を返すものです。例えば、以下のようなオブジェクト型 Person
があるとします:
interface Person {
name: string;
age: number;
}
この場合、keyof Person
は "name" | "age"
という型になります。つまり、Person
のキーとしてあり得る文字列リテラルの集合です。
extends keyof
extends keyof
は、ジェネリック型のパラメータに制約を与えるために使用されます。例えば、以下のような関数を考えてみましょう:
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
この関数 getProperty
は、オブジェクト obj
とそのキー key
を受け取り、そのキーに対応するプロパティの値を返します。ここで、K extends keyof T
の部分は、K
が T
のキーのうちのひとつであることを保証しています。これにより、コンパイラは、obj[key]
のアクセスが有効であることをチェックすることができます。
in keyof
in keyof
は、インデックスシグネチャ(indexed access types)で使用されます。インデックスシグネチャは、オブジェクトの任意のプロパティにアクセスするための方法を提供します。例えば、以下のようなインターフェースを考えてみましょう:
interface Dictionary<T> {
[key in keyof T]: T[key];
}
この Dictionary
インターフェースは、任意の型 T
のプロパティをすべて持つオブジェクトを表します。key in keyof T
の部分は、key
が T
のキーのうちのひとつであることを保証し、そのキーに対応する値の型が T[key]
であることを指定しています。
in keyof
は、インデックスシグネチャで使用され、オブジェクトの任意のプロパティにアクセスするための型定義を可能にします。extends keyof
は、ジェネリック型のパラメータにキーの制約を課します。keyof
演算子は、オブジェクトのキーのユニオン型を返します。
interface Person {
name: string;
age: number;
}
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const person: Person = { name: 'Alice', age: 30 };
const name = getProperty(person, 'name'); // name の型は string
上記の例では、getProperty
関数は、オブジェクト obj
とそのキー key
を受け取ります。K extends keyof T
の制約により、key
は Person
のキーである 'name'
または 'age'
のいずれかしか取ることができません。これにより、obj[key]
のアクセスが常に有効であることが保証されます。
interface Dictionary<T> {
[key in keyof T]: T[key];
}
const personDict: Dictionary<Person> = {
name: 'Bob',
age: 25
};
// personDict['name'] の型は string
// personDict['age'] の型は number
TypeScript 型の深掘り: 具体的な例
TypeScript の型システムは非常に強力で、複雑な型を定義することができます。以下にいくつかの例を示します。
条件付き型
type IsString<T> = T extends string ? true : false;
type A = IsString<'hello'>; // A の型は true
type B = IsString<number>; // B の型は false
マッピング型
type Partial<T> = {
[P in keyof T]?: T[P];
};
interface Person {
name: string;
age: number;
}
type PartialPerson = Partial<Person>; // { name?: string; age?: number; }
インデックスアクセス型
type PersonName = Person['name']; // PersonName の型は string
-
ジェネリック型とインターフェースの組み合わせ
- ジェネリック型でオブジェクトの型を抽象化し、インターフェースでキーの制約を定義します。
- しかし、この方法は、
extends keyof
より冗長になる可能性があります。
-
型ガード
typeof
演算子やinstanceof
演算子を使って、オブジェクトのキーの型を判断します。- ただし、これはコンパイル時の型チェックではなく、実行時のチェックになります。
in keyof
の主な用途は、インデックスシグネチャの定義です。代替手法として、以下のような方法があります:
- 個別プロパティの定義
- 必要なプロパティを個別に定義します。
- しかし、多くのプロパティがある場合、冗長になります。
- Record ユーティリティ型
Record
ユーティリティ型を使って、オブジェクトの型を定義します。- しかし、この方法は、
in keyof
よりも柔軟性が低い場合があります。
TypeScript の型システムは非常に強力ですが、複雑な型定義が必要な場合、以下のような代替手法が考えられます:
- 型エイリアス
- ユーティリティ型
- カスタム型ガード
- 条件付き型
typescript keyof