TypeScript

高度な型

TypeScript

Union、Intersection、Conditional Types

Union / Intersection 型

| と & を使った型の合成

union-intersection.ts typescript
// Union型(どちらかの型)
type ID = string | number;
type Result<T> = T | Error;
type Theme = 'light' | 'dark' | 'system';

// 型ガードで絞り込み
function processId(id: ID) {
    if (typeof id === 'string') {
        id.toUpperCase(); // string 確定
    } else {
        id.toFixed(0);    // number 確定
    }
}

// Discriminated Union(タグ付きUnion)
type Shape =
    | { kind: 'circle';    radius: number }
    | { kind: 'rectangle'; width: number; height: number }
    | { kind: 'triangle';  base: number;  height: number };

function area(shape: Shape): number {
    switch (shape.kind) {
        case 'circle':    return Math.PI * shape.radius ** 2;
        case 'rectangle': return shape.width * shape.height;
        case 'triangle':  return (shape.base * shape.height) / 2;
    }
}

// Intersection型(両方の型を持つ)
type WithId        = { id: number };
type WithTimestamp = { createdAt: Date; updatedAt: Date };
type Entity = WithId & WithTimestamp;
// Entity = { id: number; createdAt: Date; updatedAt: Date }

条件型

T extends U ? X : Y パターン

conditional.ts typescript
// 基本構文: T extends U ? TrueType : FalseType
type IsString<T> = T extends string ? true : false;
type A = IsString<string>;  // true
type B = IsString<number>;  // false

// Distribution(Unionに対して分配)
type ToArray<T> = T extends unknown ? T[] : never;
type C = ToArray<string | number>; // string[] | number[]

// NonNullable の実装
type NonNullable<T> = T extends null | undefined ? never : T;

// infer — 型の推論
type ElementType<T> = T extends (infer E)[] ? E : never;
type D = ElementType<string[]>; // string

type UnpackPromise<T> = T extends Promise<infer R> ? R : T;
type E = UnpackPromise<Promise<User>>; // User
type F = UnpackPromise<string>;        // string

// 深いオプショナル(再帰的条件型)
type DeepPartial<T> = T extends object
    ? { [K in keyof T]?: DeepPartial<T[K]> }
    : T;

Mapped Types

既存の型のプロパティをマッピング

mapped.ts typescript
// 基本構文: { [K in keyof T]: ... }
type Stringify<T> = { [K in keyof T]: string };
type Nullable<T>  = { [K in keyof T]: T[K] | null };

// +? / -? で省略可能を追加・削除
type AllOptional<T> = { [K in keyof T]+?: T[K] };
type AllRequired<T> = { [K in keyof T]-?: T[K] };

// readonly の追加・削除
type Freeze<T>  = { +readonly [K in keyof T]: T[K] };
type Mutable<T> = { -readonly [K in keyof T]: T[K] };

// キーの再マッピング(as)
type Getters<T> = {
    [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};
// { getName: () => string; getAge: () => number; ... }

// フィルタリング
type PickByValue<T, V> = {
    [K in keyof T as T[K] extends V ? K : never]: T[K];
};
type StringProps = PickByValue<User, string>; // name: string; email: string;