はじめに
TypeScriptのジェネリック型は、再利用性の高いコンポーネントや関数を作成するための強力な機能です。ジェネリック型を使うことで、さまざまな型に対して動作するコードを記述できます。具体的には、関数やクラス、インターフェースにおいて、型をパラメータとして受け取ることができます。
ジェネリック型を使うと以下のようなメリットがあります。
- 再利用性: ジェネリック型を使うことで、同じコードを異なるデータ型で再利用できます。
- 型安全性: 型を明示することで、コンパイル時に型チェックが行われ、不正な型の使用を防ぐことができます。
- 柔軟性: ジェネリック型は、特定の型に依存しない柔軟なコードを書くのに役立ちます。
様々なジェネリック型
関数のジェネリック型
まず、ジェネリック型を使わない関数の例を見てみましょう。
function identity(arg: number): number {
return arg;
}
この関数は、引数としてnumber
型を受け取り、number
型を返します。これをジェネリック型を使って記述すると、以下のようになります。
function identity<T>(arg: T): T {
return arg;
}
この例では、<T>
がジェネリック型パラメータを表しています。この関数は、引数として任意の型T
を受け取り、その型をそのまま返します。
let output1 = identity<string>("Hello");
let output2 = identity<number>(42);
console.log(output1); // "Hello"
console.log(output2); // 42
上記の例では、identity<string>
とidentity<number>
で、それぞれstring
型とnumber
型の引数を渡しています。型パラメータT
は、それぞれの呼び出しで具体的な型に置き換えられます。
クラスのジェネリック型
クラスでもジェネリック型を使うことができます。
class Box<T> {
contents: T;
constructor(value: T) {
this.contents = value;
}
getContents(): T {
return this.contents;
}
}
const stringBox = new Box<string>("Hello");
console.log(stringBox.getContents()); // "Hello"
const numberBox = new Box<number>(42);
console.log(numberBox.getContents()); // 42
この例では、Box
クラスがジェネリック型T
を持ち、contents
プロパティやgetContents
メソッドで使用しています。
インターフェースのジェネリック型
インターフェースにもジェネリック型を適用できます。
interface Pair<T, U> {
first: T;
second: U;
}
const pair: Pair<string, number> = {
first: "apple",
second: 2,
};
console.log(pair); // { first: "apple", second: 2 }
この例では、Pair
インターフェースが2つのジェネリック型パラメータT
とU
を持ち、それぞれfirst
とsecond
プロパティの型として使用しています。
制約付きジェネリック型
ジェネリック型とextends
を組み合わせると、型パラメータに制約を設けることができます。これにより、ジェネリック型が特定の条件を満たす型に限定されるため、型安全性を高めたり、特定のプロパティやメソッドが存在する型に対してのみ操作を行うことができます。
extends
は、ジェネリック型の型パラメータに制約を加えるために使用されます。これにより、ジェネリック型がある型のサブセットや特定のインターフェースを満たす必要があることを示します。
interface Lengthwise {
length: number;
}
function logLength<T extends Lengthwise>(arg: T): T {
console.log(arg.length);
return arg;
}
logLength("Hello"); // 5
logLength([1, 2, 3]); // 3
// logLength(10); // エラー: 'number'型は'Lengthwise'に割り当てられない
この例では、T extends Lengthwise
によって、T
型がLengthwise
インターフェースを満たす型である必要があります。string
やnumber[]
はLengthwise
を満たす型なので、問題なく動作しますが、number
型はLengthwise
に適合しないためエラーになります。
型パラメータの命名規則
通常、ジェネリック型の文字は1文字の大文字であり、意味が直感的である必要はありません。T
,U
,V
などが一般的です。
型パラメータの命名は、その型パラメータが何を表しているのかを示すようにすると、コードの可読性が向上します。例えば、キーを示す型パラメータはK
、値を示す型パラメータはV
を使用するのが一般的です。
複数の型パラメータが必要な場合、T
,U
,V
などを使って、役割を示すことが多いです。命名に一貫性を持たせることが重要です。
一般的によく使用される型パラメータの文字
T
一般的な型パラメータとして最もよく使われる文字です。T
は「Type」の略で、特定の型を示します。
function identity<T>(arg: T): T {
return arg;
}
U
複数の型パラメータがある場合、T
に続く第二の型パラメータとしてよく使われます。U
は「Unknown」の略と考えられることもありますが、特定の意味はありません。
function combine<T, U>(a: T, b: U): [T, U] {
return [a, b];
}
K
キーを示す型パラメータとして使用されることが多いです。特にオブジェクトのキーやマップのキーを扱う際に使われます。
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
V
値を示す型パラメータとして使用されることが多いです。特にマップの値を扱う際に使われます。
function getValue<K, V>(map: Map<K, V>, key: K): V | undefined {
return map.get(key);
}
E
エラーや例外を示す型パラメータとして使われることがあります。
function handleError<E>(error: E): void {
console.error(error);
}
R
戻り値の型を示す型パラメータとしてよく使われます。Rは「Return」の略です。
function process<T, R>(input: T, processor: (value: T) => R): R {
return processor(input);
}
P
プロパティやパラメータを示す型パラメータとして使われることがあります。
function pickProperty<T, P extends keyof T>(obj: T, property: P): T[P] {
return obj[property];
}
まとめ
ジェネリック型をうまく活用することで、柔軟かつ型安全なコードを書くことができます。TypeScriptを使う際には、ぜひジェネリック型を活用してみてください。