ジェネリクスは、型をパラメータとして扱い、再利用可能なコンポーネントを作成するための強力な機能です。
// ジェネリック関数
function identity<T>(value: T): T {
return value;
}
// 使用例 - 型を明示的に指定
const stringResult = identity<string>("Hello");
const numberResult = identity<number>(42);
// 型推論による使用
const inferredString = identity("World"); // string型と推論される
const inferredNumber = identity(100); // number型と推論される
// より実用的な例:配列の最初の要素を返す
function getFirstElement<T>(array: T[]): T | undefined {
return array[0];
}
const firstNumber = getFirstElement([1, 2, 3]); // number | undefined
const firstString = getFirstElement(["a", "b", "c"]); // string | undefined// 複数の型パラメータを持つ関数
function pair<T, U>(first: T, second: U): [T, U] {
return [first, second];
}
const result1 = pair("hello", 123); // [string, number]
const result2 = pair(true, { id: 1 }); // [boolean, { id: number }]
// キーと値のペアを作成
function createKeyValuePair<K, V>(key: K, value: V): { key: K; value: V } {
return { key, value };
}
const kvp1 = createKeyValuePair("name", "TypeScript");
const kvp2 = createKeyValuePair(1, ["apple", "banana"]);// ジェネリッククラスの定義
class Box<T> {
private content: T;
constructor(content: T) {
this.content = content;
}
getContent(): T {
return this.content;
}
setContent(content: T): void {
this.content = content;
}
}
// 使用例
const stringBox = new Box<string>("Hello");
const numberBox = new Box<number>(42);
const objectBox = new Box<{ name: string }>({ name: "TypeScript" });
console.log(stringBox.getContent()); // "Hello"
console.log(numberBox.getContent()); // 42
// より実践的な例:スタック
class Stack<T> {
private items: T[] = [];
push(item: T): void {
this.items.push(item);
}
pop(): T | undefined {
return this.items.pop();
}
peek(): T | undefined {
return this.items[this.items.length - 1];
}
isEmpty(): boolean {
return this.items.length === 0;
}
size(): number {
return this.items.length;
}
}
const numberStack = new Stack<number>();
numberStack.push(1);
numberStack.push(2);
numberStack.push(3);
console.log(numberStack.pop()); // 3
console.log(numberStack.peek()); // 2型パラメータに制約を追加して、特定の条件を満たす型のみを受け入れるようにできます。
// 基本的な制約
interface Lengthwise {
length: number;
}
function logLength<T extends Lengthwise>(value: T): T {
console.log(`長さ: ${value.length}`);
return value;
}
logLength("Hello"); // OK: stringにはlengthプロパティがある
logLength([1, 2, 3]); // OK: 配列にはlengthプロパティがある
logLength({ length: 10 }); // OK: lengthプロパティを持つオブジェクト
// logLength(123); // エラー: numberにはlengthプロパティがない
// keyof制約
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const person = { name: "Alice", age: 30, email: "alice@example.com" };
const name = getProperty(person, "name"); // string
const age = getProperty(person, "age"); // number
// const invalid = getProperty(person, "id"); // エラー: "id"はpersonのキーではない
// 複数の制約
interface Comparable<T> {
compareTo(other: T): number;
}
class SortableArray<T extends Comparable<T>> {
private items: T[] = [];
add(item: T): void {
this.items.push(item);
}
sort(): T[] {
return this.items.sort((a, b) => a.compareTo(b));
}
}
// 使用例
class Version implements Comparable<Version> {
constructor(private major: number, private minor: number) {}
compareTo(other: Version): number {
if (this.major !== other.major) {
return this.major - other.major;
}
return this.minor - other.minor;
}
toString(): string {
return `${this.major}.${this.minor}`;
}
}
const versions = new SortableArray<Version>();
versions.add(new Version(2, 1));
versions.add(new Version(1, 9));
versions.add(new Version(2, 0));
console.log(versions.sort().map(v => v.toString())); // ["1.9", "2.0", "2.1"]// ジェネリックインターフェース
interface Container<T> {
value: T;
getValue(): T;
setValue(value: T): void;
}
// 実装
class StringContainer implements Container<string> {
value: string;
constructor(value: string) {
this.value = value;
}
getValue(): string {
return this.value;
}
setValue(value: string): void {
this.value = value;
}
}
// ジェネリック関数型インターフェース
interface Transformer<T, U> {
(input: T): U;
}
const numberToString: Transformer<number, string> = (num) => num.toString();
const stringToNumber: Transformer<string, number> = (str) => parseInt(str);
// より複雑な例
interface Repository<T> {
findById(id: string): T | undefined;
findAll(): T[];
save(item: T): void;
delete(id: string): boolean;
}
interface Entity {
id: string;
}
class InMemoryRepository<T extends Entity> implements Repository<T> {
private items: Map<string, T> = new Map();
findById(id: string): T | undefined {
return this.items.get(id);
}
findAll(): T[] {
return Array.from(this.items.values());
}
save(item: T): void {
this.items.set(item.id, item);
}
delete(id: string): boolean {
return this.items.delete(id);
}
}条件型を使用すると、型レベルでの条件分岐が可能になります。
// 基本的な条件型
type IsString<T> = T extends string ? true : false;
type Test1 = IsString<string>; // true
type Test2 = IsString<number>; // false
type Test3 = IsString<"hello">; // true
// より実用的な例
type NonNullable<T> = T extends null | undefined ? never : T;
type A = NonNullable<string | null>; // string
type B = NonNullable<number | undefined>; // number
type C = NonNullable<null>; // never
// 配列要素の型を抽出
type ElementType<T> = T extends (infer E)[] ? E : T;
type E1 = ElementType<string[]>; // string
type E2 = ElementType<number[]>; // number
type E3 = ElementType<string>; // string
// 関数の戻り値の型を抽出
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
type R1 = ReturnType<() => string>; // string
type R2 = ReturnType<(x: number) => boolean>; // boolean
type R3 = ReturnType<typeof Math.random>; // number
// 条件型の分配
type ToArray<T> = T extends any ? T[] : never;
type T1 = ToArray<string | number>; // string[] | number[]
type T2 = ToArray<string>; // string[]既存の型から新しい型を作成するための強力な機能です。
// 基本的なマップ型
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};
interface User {
name: string;
age: number;
email: string;
}
type ReadonlyUser = Readonly<User>;
// {
// readonly name: string;
// readonly age: number;
// readonly email: string;
// }
// オプショナルにするマップ型
type Partial<T> = {
[P in keyof T]?: T[P];
};
type PartialUser = Partial<User>;
// {
// name?: string;
// age?: number;
// email?: string;
// }
// 必須にするマップ型
type Required<T> = {
[P in keyof T]-?: T[P];
};
// Nullable型を作成
type Nullable<T> = {
[P in keyof T]: T[P] | null;
};
// 値の型を変換するマップ型
type Stringify<T> = {
[P in keyof T]: string;
};
type StringifiedUser = Stringify<User>;
// {
// name: string;
// age: string;
// email: string;
// }
// 条件付きマップ型
type NullableKeys<T> = {
[K in keyof T]: null extends T[K] ? K : never;
}[keyof T];
type Example = {
a: string;
b: string | null;
c: number;
d: number | null;
};
type NullableKeysOfExample = NullableKeys<Example>; // "b" | "d"文字列リテラル型を動的に構築できます。
// 基本的なテンプレートリテラル型
type Greeting = `Hello, ${string}!`;
const greeting1: Greeting = "Hello, World!"; // OK
const greeting2: Greeting = "Hello, TypeScript!"; // OK
// const greeting3: Greeting = "Hi, World!"; // エラー
// 複数の組み合わせ
type Method = "get" | "post" | "put" | "delete";
type Resource = "user" | "post" | "comment";
type Endpoint = `/${Resource}/${Method}`;
const endpoint1: Endpoint = "/user/get"; // OK
const endpoint2: Endpoint = "/post/delete"; // OK
// const endpoint3: Endpoint = "/user/patch"; // エラー
// 大文字変換
type Uppercase<S extends string> = S extends `${infer First}${infer Rest}`
? `${Uppercase<First>}${Uppercase<Rest>}`
: S;
type UppercaseGreeting = Uppercase<"hello world">; // "HELLO WORLD"
// イベントハンドラーの型を生成
type EventName = "click" | "focus" | "blur";
type EventHandler<T extends string> = `on${Capitalize<T>}`;
type Handlers = {
[K in EventHandler<EventName>]: (event: Event) => void;
};
// {
// onClick: (event: Event) => void;
// onFocus: (event: Event) => void;
// onBlur: (event: Event) => void;
// }
// CSSプロパティの型
type CSSUnit = "px" | "em" | "rem" | "%";
type CSSValue = `${number}${CSSUnit}`;
const width: CSSValue = "100px"; // OK
const height: CSSValue = "2em"; // OK
const margin: CSSValue = "50%"; // OK
// const padding: CSSValue = "10"; // エラーTypeScriptには多くの組み込みユーティリティ型があります。
// Pick - 特定のプロパティのみを選択
interface Todo {
title: string;
description: string;
completed: boolean;
createdAt: Date;
}
type TodoPreview = Pick<Todo, "title" | "completed">;
// {
// title: string;
// completed: boolean;
// }
// Omit - 特定のプロパティを除外
type TodoInfo = Omit<Todo, "completed" | "createdAt">;
// {
// title: string;
// description: string;
// }
// Record - キーと値の型からオブジェクト型を作成
type PageInfo = {
title: string;
url: string;
};
type Pages = Record<"home" | "about" | "contact", PageInfo>;
// {
// home: PageInfo;
// about: PageInfo;
// contact: PageInfo;
// }
// Exclude - ユニオン型から特定の型を除外
type T1 = Exclude<"a" | "b" | "c", "a">; // "b" | "c"
type T2 = Exclude<string | number | (() => void), Function>; // string | number
// Extract - ユニオン型から特定の型を抽出
type T3 = Extract<"a" | "b" | "c", "a" | "f">; // "a"
type T4 = Extract<string | number | (() => void), Function>; // () => void
// Parameters - 関数の引数の型を取得
function greet(name: string, age: number): string {
return `Hello ${name}, you are ${age} years old`;
}
type GreetParams = Parameters<typeof greet>; // [string, number]
// ConstructorParameters - コンストラクタの引数の型を取得
class Person {
constructor(public name: string, public age: number) {}
}
type PersonParams = ConstructorParameters<typeof Person>; // [string, number]
// InstanceType - コンストラクタ関数のインスタンス型を取得
type PersonInstance = InstanceType<typeof Person>; // Person// Deep Partial - ネストしたオブジェクトも含めてオプショナルにする
type DeepPartial<T> = {
[P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];
};
interface NestedObject {
a: {
b: {
c: string;
d: number;
};
e: boolean;
};
}
type PartialNested = DeepPartial<NestedObject>;
// {
// a?: {
// b?: {
// c?: string;
// d?: number;
// };
// e?: boolean;
// };
// }
// Deep Readonly - ネストしたオブジェクトも含めて読み取り専用にする
type DeepReadonly<T> = {
readonly [P in keyof T]: T[P] extends object ? DeepReadonly<T[P]> : T[P];
};
// Flatten - 配列の型を平坦化
type Flatten<T> = T extends Array<infer U> ? U : T;
type F1 = Flatten<string[][]>; // string[]
type F2 = Flatten<number[]>; // number
type F3 = Flatten<string>; // string
// UnionToIntersection - ユニオン型を交差型に変換
type UnionToIntersection<U> = (
U extends any ? (k: U) => void : never
) extends (k: infer I) => void ? I : never;
type U1 = { a: string } | { b: number } | { c: boolean };
type I1 = UnionToIntersection<U1>; // { a: string } & { b: number } & { c: boolean }// const アサーション
const colors = ["red", "green", "blue"] as const;
// readonly ["red", "green", "blue"]
const config = {
endpoint: "https://api.example.com",
timeout: 5000,
retries: 3
} as const;
// {
// readonly endpoint: "https://api.example.com";
// readonly timeout: 5000;
// readonly retries: 3;
// }
// satisfies演算子 (TypeScript 4.9+)
type Colors = "red" | "green" | "blue";
type RGB = [red: number, green: number, blue: number];
const palette = {
red: [255, 0, 0],
green: "#00ff00",
blue: [0, 0, 255]
} satisfies Record<Colors, string | RGB>;
// 型推論の活用
function createAction<T extends string>(type: T) {
return function<P>(payload: P) {
return { type, payload } as const;
};
}
const addTodo = createAction("ADD_TODO");
const action = addTodo({ text: "Learn TypeScript" });
// {
// readonly type: "ADD_TODO";
// readonly payload: { text: string };
// }配列から重複を除去する関数を作成してください。型安全性を保ちながら、任意の型の配列で動作するようにしてください。
キーと値のペアを保存するMapのようなデータ構造を実装してください。get、set、has、deleteメソッドを含めてください。
オブジェクトから関数型のプロパティのキーのみを抽出する型を作成してください。
オブジェクトのすべてのプロパティをPromiseでラップする型を作成してください。
// 基本的な実装
function removeDuplicates<T>(array: T[]): T[] {
return Array.from(new Set(array));
}
// より高度な実装(カスタム比較関数付き)
function removeDuplicatesBy<T, K>(
array: T[],
keySelector: (item: T) => K
): T[] {
const seen = new Map<K, T>();
for (const item of array) {
const key = keySelector(item);
if (!seen.has(key)) {
seen.set(key, item);
}
}
return Array.from(seen.values());
}
// テスト
const numbers = [1, 2, 2, 3, 3, 3, 4];
console.log(removeDuplicates(numbers)); // [1, 2, 3, 4]
const users = [
{ id: 1, name: "Alice" },
{ id: 2, name: "Bob" },
{ id: 1, name: "Alice (duplicate)" },
{ id: 3, name: "Charlie" }
];
const uniqueUsers = removeDuplicatesBy(users, user => user.id);
console.log(uniqueUsers);
// [
// { id: 1, name: "Alice" },
// { id: 2, name: "Bob" },
// { id: 3, name: "Charlie" }
// ]
// さらに高度な実装(複数のキーでの重複除去)
function removeDuplicatesByMultipleKeys<T>(
array: T[],
...keySelectors: Array<(item: T) => any>
): T[] {
const seen = new Set<string>();
const result: T[] = [];
for (const item of array) {
const keys = keySelectors.map(selector => {
const value = selector(item);
return typeof value === 'object' ? JSON.stringify(value) : String(value);
});
const compositeKey = keys.join('|');
if (!seen.has(compositeKey)) {
seen.add(compositeKey);
result.push(item);
}
}
return result;
}
const products = [
{ category: "electronics", brand: "Apple", name: "iPhone" },
{ category: "electronics", brand: "Apple", name: "iPad" },
{ category: "electronics", brand: "Apple", name: "iPhone" }, // 重複
{ category: "electronics", brand: "Samsung", name: "Galaxy" }
];
const uniqueProducts = removeDuplicatesByMultipleKeys(
products,
p => p.category,
p => p.brand,
p => p.name
);
console.log(uniqueProducts); // 重複が除去されるclass CustomMap<K, V> {
private items: Map<K, V> = new Map();
// 値を設定
set(key: K, value: V): this {
this.items.set(key, value);
return this;
}
// 値を取得
get(key: K): V | undefined {
return this.items.get(key);
}
// キーの存在確認
has(key: K): boolean {
return this.items.has(key);
}
// エントリを削除
delete(key: K): boolean {
return this.items.delete(key);
}
// すべてクリア
clear(): void {
this.items.clear();
}
// サイズを取得
get size(): number {
return this.items.size;
}
// すべてのキーを取得
keys(): K[] {
return Array.from(this.items.keys());
}
// すべての値を取得
values(): V[] {
return Array.from(this.items.values());
}
// すべてのエントリを取得
entries(): Array<[K, V]> {
return Array.from(this.items.entries());
}
// forEach
forEach(callback: (value: V, key: K, map: CustomMap<K, V>) => void): void {
this.items.forEach((value, key) => {
callback(value, key, this);
});
}
// map操作
map<U>(mapper: (value: V, key: K) => U): CustomMap<K, U> {
const result = new CustomMap<K, U>();
this.forEach((value, key) => {
result.set(key, mapper(value, key));
});
return result;
}
// filter操作
filter(predicate: (value: V, key: K) => boolean): CustomMap<K, V> {
const result = new CustomMap<K, V>();
this.forEach((value, key) => {
if (predicate(value, key)) {
result.set(key, value);
}
});
return result;
}
// デフォルト値付きget
getOrDefault(key: K, defaultValue: V): V {
return this.has(key) ? this.get(key)! : defaultValue;
}
// 計算された値で初期化
computeIfAbsent(key: K, mappingFunction: (key: K) => V): V {
if (this.has(key)) {
return this.get(key)!;
}
const value = mappingFunction(key);
this.set(key, value);
return value;
}
}
// 使用例
const map = new CustomMap<string, number>();
// メソッドチェーン
map.set("one", 1)
.set("two", 2)
.set("three", 3);
console.log(map.get("two")); // 2
console.log(map.has("four")); // false
// map操作
const doubled = map.map(value => value * 2);
console.log(doubled.get("two")); // 4
// filter操作
const filtered = map.filter(value => value > 1);
console.log(filtered.keys()); // ["two", "three"]
// computeIfAbsent
const cache = new CustomMap<string, string[]>();
const words = cache.computeIfAbsent("fruits", () => ["apple", "banana"]);
console.log(words); // ["apple", "banana"]
// 型の異なるマップ
const userMap = new CustomMap<number, { name: string; age: number }>();
userMap.set(1, { name: "Alice", age: 30 });
userMap.set(2, { name: "Bob", age: 25 });
const names = userMap.map(user => user.name);
console.log(names.values()); // ["Alice", "Bob"]// 関数型プロパティのキーを抽出する型
type FunctionPropertyNames<T> = {
[K in keyof T]: T[K] extends Function ? K : never;
}[keyof T];
// テスト用のインターフェース
interface Example {
name: string;
age: number;
greet(): void;
calculate(x: number): number;
isActive: boolean;
getData: () => string;
}
type FunctionKeys = FunctionPropertyNames<Example>;
// "greet" | "calculate" | "getData"
// より詳細な実装(メソッドとプロパティ関数を区別)
type MethodPropertyNames<T> = {
[K in keyof T]: T[K] extends (...args: any[]) => any ? K : never;
}[keyof T];
type PropertyFunctionNames<T> = {
[K in keyof T]: T[K] extends Function
? T[K] extends (...args: any[]) => any
? never
: K
: never;
}[keyof T];
// 非関数プロパティのみを抽出
type NonFunctionPropertyNames<T> = {
[K in keyof T]: T[K] extends Function ? never : K;
}[keyof T];
type NonFunctionKeys = NonFunctionPropertyNames<Example>;
// "name" | "age" | "isActive"
// 特定の戻り値型を持つ関数プロパティを抽出
type FunctionPropertyNamesWithReturnType<T, R> = {
[K in keyof T]: T[K] extends (...args: any[]) => R ? K : never;
}[keyof T];
interface MixedMethods {
getString(): string;
getNumber(): number;
getBoolean(): boolean;
processData(data: any): void;
calculateSum(a: number, b: number): number;
}
type StringReturningMethods = FunctionPropertyNamesWithReturnType<MixedMethods, string>;
// "getString"
type NumberReturningMethods = FunctionPropertyNamesWithReturnType<MixedMethods, number>;
// "getNumber" | "calculateSum"
// 実用的なユーティリティ型
type PickMethods<T> = Pick<T, FunctionPropertyNames<T>>;
type OmitMethods<T> = Omit<T, FunctionPropertyNames<T>>;
type OnlyMethods = PickMethods<Example>;
// {
// greet(): void;
// calculate(x: number): number;
// getData: () => string;
// }
type OnlyProperties = OmitMethods<Example>;
// {
// name: string;
// age: number;
// isActive: boolean;
// }// すべてのプロパティをPromiseでラップする型
type Promisify<T> = {
[P in keyof T]: Promise<T[P]>;
};
interface SyncData {
id: number;
name: string;
isActive: boolean;
metadata: {
created: Date;
updated: Date;
};
}
type AsyncData = Promisify<SyncData>;
// {
// id: Promise<number>;
// name: Promise<string>;
// isActive: Promise<boolean>;
// metadata: Promise<{
// created: Date;
// updated: Date;
// }>;
// }
// 深いPromise化
type DeepPromisify<T> = T extends object
? T extends (...args: any[]) => any
? T // 関数はそのまま
: {
[P in keyof T]: DeepPromisify<T[P]>;
}
: Promise<T>;
type DeepAsyncData = DeepPromisify<SyncData>;
// {
// id: Promise<number>;
// name: Promise<string>;
// isActive: Promise<boolean>;
// metadata: {
// created: Promise<Date>;
// updated: Promise<Date>;
// };
// }
// 実用的な実装:非同期化クラス
class AsyncWrapper<T extends object> {
constructor(private source: T) {}
get<K extends keyof T>(key: K): Promise<T[K]> {
return Promise.resolve(this.source[key]);
}
getAll(): Promisify<T> {
const result = {} as Promisify<T>;
for (const key in this.source) {
if (this.source.hasOwnProperty(key)) {
result[key] = Promise.resolve(this.source[key]);
}
}
return result;
}
}
// 条件付きPromise化(既にPromiseの場合はそのまま)
type SmartPromisify<T> = {
[P in keyof T]: T[P] extends Promise<any> ? T[P] : Promise<T[P]>;
};
interface MixedData {
syncValue: string;
asyncValue: Promise<number>;
nested: {
sync: boolean;
async: Promise<string>;
};
}
type SmartAsyncData = SmartPromisify<MixedData>;
// {
// syncValue: Promise<string>;
// asyncValue: Promise<number>; // 既にPromiseなのでそのまま
// nested: Promise<{
// sync: boolean;
// async: Promise<string>;
// }>;
// }
// Promise化とメソッドの保持
type PromisifyMethods<T> = {
[K in keyof T]: T[K] extends (...args: infer A) => infer R
? (...args: A) => Promise<R>
: Promise<T[K]>;
};
interface Service {
data: string;
count: number;
getData(): string;
processData(input: string): number;
}
type AsyncService = PromisifyMethods<Service>;
// {
// data: Promise<string>;
// count: Promise<number>;
// getData(): Promise<string>;
// processData(input: string): Promise<number>;
// }
// 実装例
function promisifyObject<T extends object>(obj: T): PromisifyMethods<T> {
const result = {} as any;
for (const key in obj) {
const value = obj[key];
if (typeof value === 'function') {
result[key] = async (...args: any[]) => {
return value.apply(obj, args);
};
} else {
result[key] = Promise.resolve(value);
}
}
return result;
}
// 使用例
const syncService: Service = {
data: "Hello",
count: 42,
getData() {
return this.data;
},
processData(input: string) {
return input.length + this.count;
}
};
const asyncService = promisifyObject(syncService);
// 非同期で使用
async function useAsyncService() {
const data = await asyncService.data; // string
const count = await asyncService.count; // number
const result = await asyncService.getData(); // string
const processed = await asyncService.processData("test"); // number
console.log({ data, count, result, processed });
}第4章では、TypeScriptのジェネリクスと高度な型について学びました:
- ジェネリクスの基本概念と使い方
- ジェネリック関数、クラス、インターフェース
- ジェネリック制約による型の制限
- 条件型による型レベルの条件分岐
- マップ型による既存の型の変換
- テンプレートリテラル型による文字列型の操作
- 組み込みユーティリティ型の活用
- 高度な型操作テクニック
- 型推論とアサーションの活用
次章では、モジュールと名前空間について詳しく学習していきます。