TypeScriptのジェネリクスとユニオン型・交差型の組み合わせ活用法
生徒
「先生、TypeScriptのジェネリクスって便利そうですが、ユニオン型や交差型と一緒に使えるんですか?」
先生
「もちろん使えますよ。ジェネリクス(Generics)とユニオン型・交差型を組み合わせることで、より柔軟で安全なコードを書くことができます。」
生徒
「なるほど!でも、ちょっと難しそうですね…。どんな場面で使うのか、具体的に知りたいです!」
先生
「それでは、ジェネリクスとユニオン型・交差型の基本から、組み合わせた使い方まで、わかりやすく説明していきましょう!」
1. ジェネリクスとは?
まず、ジェネリクス(Generics)とは、型をあとから指定できる仕組みのことです。TypeScriptでは、関数やクラス、インターフェースなどに柔軟な型を持たせたいときに使います。たとえば「どんな型でも受け取れる関数」を作りたい場合に便利です。
次の例を見てみましょう。
function identity<T>(value: T): T {
return value;
}
console.log(identity("文字列"));
console.log(identity(123));
文字列
123
この<T>という部分がジェネリクスです。ここでのTは「型の変数」のようなもので、実際に使うときに「文字列型」や「数値型」などが代入されます。
2. ユニオン型とは?
ユニオン型(Union Type)とは、「複数の型のどれかである」という意味です。つまり、「文字列でも数値でもOK」といった柔軟な型を作ることができます。
例えば次のように書けます。
let value: string | number;
value = "こんにちは";
value = 100;
このように「|」でつなげることで、複数の型を許容できるようになります。
3. 交差型とは?
交差型(Intersection Type)は、「複数の型をすべて持つ」という意味です。つまり「Aの型の性質もBの型の性質も持つオブジェクト」を表します。
たとえば次のように使います。
type Person = { name: string };
type Contact = { email: string };
type PersonWithContact = Person & Contact;
const user: PersonWithContact = {
name: "田中",
email: "tanaka@example.com"
};
交差型は「&」を使って複数の型を組み合わせるのが特徴です。
4. ジェネリクス × ユニオン型の活用
ここからが本題です。ジェネリクスとユニオン型を組み合わせることで、「複数の型のどれかを受け取る汎用的な関数」を作ることができます。
function printValue<T extends string | number>(value: T): void {
console.log(value);
}
printValue("テキスト");
printValue(42);
テキスト
42
このようにT extends string | numberと書くことで、「Tはstringまたはnumberのどちらか」という制約を付けています。これにより、他の型(例えばbooleanなど)は受け付けないようにできます。
この組み合わせは、APIの引数チェックやフォーム入力の型安全性を保つ場面などで非常に便利です。
5. ジェネリクス × 交差型の活用
次に、ジェネリクスと交差型の組み合わせを見てみましょう。これは「異なる型の情報をまとめて扱いたい」ときに使います。
function mergeObjects<T, U>(obj1: T, obj2: U): T & U {
return { ...obj1, ...obj2 };
}
const person = { name: "山田" };
const contact = { email: "yamada@example.com" };
const merged = mergeObjects(person, contact);
console.log(merged.name);
console.log(merged.email);
山田
yamada@example.com
この関数は、2つのオブジェクトを受け取り、それらを結合して新しいオブジェクトを返します。戻り値の型がT & U(交差型)になっているので、両方の型のプロパティが使えるようになります。
つまり、ジェネリクスが「型を柔軟に受け取る」役割を果たし、交差型が「型を合体させる」役割を果たしているのです。
6. 応用例:ユニオン型と交差型を組み合わせた型制御
さらに応用すると、ユニオン型と交差型を組み合わせて、より高度な型制御を行うこともできます。たとえば、「特定のキーを持っているかどうか」で動作を変えるような関数です。
type User = { id: number; name: string };
type Admin = { id: number; name: string; role: "admin" };
function getUserInfo<T extends User | Admin>(user: T): string {
if ("role" in user) {
return `管理者: ${user.name}`;
}
return `ユーザー: ${user.name}`;
}
console.log(getUserInfo({ id: 1, name: "太郎" }));
console.log(getUserInfo({ id: 2, name: "花子", role: "admin" }));
ユーザー: 太郎
管理者: 花子
このようにジェネリクスとユニオン型・交差型を使うことで、柔軟かつ安全に異なるデータ構造を扱えるようになります。
TypeScriptの型システムを理解すると、プログラムのエラーを事前に防ぐことができ、安心して開発を進められるようになります。
まとめ
ジェネリクスとユニオン型・交差型を組み合わせる意味
ここまで、TypeScriptにおけるジェネリクス、ユニオン型、交差型の基本的な考え方と、それらを組み合わせて使う方法について解説してきました。ジェネリクスは「型をあとから決められる仕組み」であり、ユニオン型は「複数の型のどれかを許容する型」、交差型は「複数の型をすべて満たす型」を表します。これらはそれぞれ単体でも便利ですが、組み合わせることでTypeScriptの型表現は一気に実践的になります。
特に実務の開発現場では、「入力値が複数パターンある」「返却データの構造が条件によって変わる」「共通のプロパティを持ちながら役割が異なるオブジェクトを扱う」といった場面が頻繁に登場します。そのようなケースで、ジェネリクスとユニオン型・交差型を適切に組み合わせることで、コードの柔軟性を保ちながら、型安全性を高いレベルで維持することができます。
ジェネリクス × ユニオン型で実現できること
ジェネリクスとユニオン型を組み合わせる最大のメリットは、「扱える型の範囲を制限しつつ、汎用的な処理を書くことができる」点にあります。たとえば、文字列または数値のみを受け付ける関数を作りたい場合、ジェネリクスにユニオン型の制約を付けることで、想定外の型が入り込むのを防げます。
これにより、APIの引数チェックやユーザー入力の検証、表示用データのフォーマット処理など、さまざまな場面で安全な関数設計が可能になります。any型に頼らず、型の意図を明確に表現できる点は、長期的に見て非常に大きなメリットです。
ジェネリクス × 交差型で設計が整理される理由
一方、ジェネリクスと交差型の組み合わせは、「複数の型情報をまとめて扱いたい」場面で力を発揮します。複数のオブジェクトを結合したり、共通のプロパティを持つデータ構造を一つにまとめたりする場合、交差型を使うことで「すべての性質を持つ型」を自然に表現できます。
ジェネリクスを使うことで、結合する対象の型を柔軟に受け取りつつ、戻り値としては両方の型を満たす安全な型を返すことができます。この考え方は、設定オブジェクトの合成や、ユーザー情報と権限情報の統合など、実務でもよく使われる設計パターンです。
総合的な活用サンプル
type BaseResponse = {
success: boolean;
};
type DataResponse<T> = BaseResponse & {
data: T;
};
type ErrorResponse = BaseResponse & {
errorMessage: string;
};
function createResponse<T>(
value: T | null
): DataResponse<T> | ErrorResponse {
if (value !== null) {
return {
success: true,
data: value
};
}
return {
success: false,
errorMessage: "データが存在しません"
};
}
const response1 = createResponse({ id: 1, name: "太郎" });
const response2 = createResponse(null);
この例では、ジェネリクス、ユニオン型、交差型をすべて組み合わせています。成功時と失敗時で異なる構造を持つレスポンスを、安全に表現できている点がポイントです。successフラグによって処理を分岐させることで、TypeScriptの型ガードとも相性の良い設計になります。
このような書き方を身につけると、「条件によって返るデータが変わる」という状況でも、型の不安を感じることなく実装できるようになります。結果として、コードの可読性と保守性が向上し、バグの混入も防ぎやすくなります。
生徒
「ジェネリクスとユニオン型、交差型って別々の知識だと思っていましたが、組み合わせるとすごく実用的なんですね。」
先生
「そうですね。TypeScriptの型は、単体よりも組み合わせて使うことで本領を発揮します。」
生徒
「特に、返り値の型を安全に表現できるのが分かって、実務で使うイメージが湧きました。」
先生
「それは良い理解です。型が整理されると、コードそのものも読みやすくなりますよ。」
生徒
「これからは、anyで逃げずに、ジェネリクスとユニオン型・交差型で表現できないか考えてみます。」
先生
「その意識が大切です。型を味方につけて、安心して書けるTypeScriptコードを目指しましょう。」