Skip to content

Latest commit

 

History

History
1053 lines (841 loc) · 26.6 KB

File metadata and controls

1053 lines (841 loc) · 26.6 KB

第4章: ジェネリクスと高度な型

4.1 ジェネリクスの基礎

ジェネリクスは、型をパラメータとして扱い、再利用可能なコンポーネントを作成するための強力な機能です。

基本的なジェネリクス

// ジェネリック関数
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"]);

4.2 ジェネリッククラス

// ジェネリッククラスの定義
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

4.3 ジェネリック制約

型パラメータに制約を追加して、特定の条件を満たす型のみを受け入れるようにできます。

// 基本的な制約
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"]

4.4 ジェネリックインターフェース

// ジェネリックインターフェース
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);
    }
}

4.5 条件型

条件型を使用すると、型レベルでの条件分岐が可能になります。

// 基本的な条件型
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[]

4.6 マップ型

既存の型から新しい型を作成するための強力な機能です。

// 基本的なマップ型
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"

4.7 テンプレートリテラル型

文字列リテラル型を動的に構築できます。

// 基本的なテンプレートリテラル型
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";  // エラー

4.8 ユーティリティ型

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

4.9 高度な型操作

// 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 }

4.10 型の推論とアサーション

// 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 };
// }

練習問題

問題1: ジェネリック関数の作成

配列から重複を除去する関数を作成してください。型安全性を保ちながら、任意の型の配列で動作するようにしてください。

問題2: ジェネリッククラスの実装

キーと値のペアを保存するMapのようなデータ構造を実装してください。get、set、has、deleteメソッドを含めてください。

問題3: 条件型の活用

オブジェクトから関数型のプロパティのキーのみを抽出する型を作成してください。

問題4: 高度な型変換

オブジェクトのすべてのプロパティをPromiseでラップする型を作成してください。

解答例

解答1: ジェネリック関数の作成

// 基本的な実装
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); // 重複が除去される

解答2: ジェネリッククラスの実装

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"]

解答3: 条件型の活用

// 関数型プロパティのキーを抽出する型
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;
// }

解答4: 高度な型変換

// すべてのプロパティを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のジェネリクスと高度な型について学びました:

  • ジェネリクスの基本概念と使い方
  • ジェネリック関数、クラス、インターフェース
  • ジェネリック制約による型の制限
  • 条件型による型レベルの条件分岐
  • マップ型による既存の型の変換
  • テンプレートリテラル型による文字列型の操作
  • 組み込みユーティリティ型の活用
  • 高度な型操作テクニック
  • 型推論とアサーションの活用

次章では、モジュールと名前空間について詳しく学習していきます。