← Back to Index

Chapter 2.4: Generics and Advanced Types

The core weapons of TypeScript type gymnastics

🆚 Difference from Java Generics: Java generics have type erasure — generic information is lost at runtime. TS's type system is entirely at compile time, but far more powerful than Java generics (conditional types, mapped types, etc. have no Java equivalent).

1. Generic Functions

Generics let you write reusable, type-safe components. Type parameters are declared with angle brackets and can be explicitly specified or automatically inferred by the compiler:

// 基本泛型函数
function identity<T>(arg: T): T {
    return arg;
}

// 显式指定类型参数
const str = identity<string>("hello");
// 类型推断——编译器自动推导 T = number
const num = identity(42);

// 多个类型参数
function pair<A, B>(first: A, second: B): [A, B] {
    return [first, second];
}
const p = pair("age", 30); // 推断为 [string, number]

// 泛型箭头函数
const toArray = <T,>(value: T): T[] => [value];

Note the comma in <T,> for arrow functions — in .tsx files this comma is needed to avoid confusion with JSX tags.

2. Generic Interfaces and Classes

// 泛型接口
interface Container<T> {
    value: T;
    getValue(): T;
}

const numBox: Container<number> = {
    value: 42,
    getValue() { return this.value; }
};

// 泛型类——实现一个类型安全的栈
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]; }
    get size(): number { return this.items.length; }
}

const numberStack = new Stack<number>();
numberStack.push(1);
numberStack.push(2);
// numberStack.push("3"); // ✗ 编译错误

3. Generic Constraints

Use the extends keyword to constrain type parameters, ensuring generics meet certain conditions:

interface HasLength {
    length: number;
}

// T 必须有 length 属性
function logLength<T extends HasLength>(arg: T): number {
    console.log(arg.length);
    return arg.length;
}
logLength("hello");    // ✓ string 有 length
logLength([1, 2, 3]);  // ✓ 数组有 length
// logLength(123);     // ✗ number 没有 length

// keyof 约束——限制 key 必须是 obj 的属性名
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
    return obj[key];
}
const user = { name: "Alice", age: 30 };
getProperty(user, "name"); // ✓ 返回 string
// getProperty(user, "email"); // ✗ "email" 不在 keyof typeof user 中

4. Common Utility Types

TypeScript includes many built-in utility types. Mastering them can significantly reduce boilerplate code:

interface User {
    id: number;
    name: string;
    email: string;
    age: number;
}

// Partial<T> —— 所有属性变为可选
type UpdateUser = Partial<User>;
// { id?: number; name?: string; email?: string; age?: number }

// Required<T> —— 所有属性变为必填
type StrictUser = Required<Partial<User>>;

// Pick<T, K> —— 选取部分属性
type UserSummary = Pick<User, "id" | "name">;
// { id: number; name: string }

// Omit<T, K> —— 排除部分属性
type UserWithoutEmail = Omit<User, "email">;

// Record<K, V> —— 构造键值对类型
type RolePermissions = Record<"admin" | "user" | "guest", string[]>;

// Readonly<T> —— 所有属性变为只读
type FrozenUser = Readonly<User>;

// ReturnType<T> / Parameters<T>
function createUser(name: string, age: number) { return { name, age }; }
type Created = ReturnType<typeof createUser>; // { name: string; age: number }
type Args = Parameters<typeof createUser>;     // [string, number]

5. Conditional Types

Conditional types let you do if/else at the type level, with syntax similar to ternary expressions:

// 基本语法:T extends U ? X : Y
type IsString<T> = T extends string ? "yes" : "no";
type A = IsString<string>;  // "yes"
type B = IsString<number>;  // "no"

// 内置条件类型的实现原理
type MyNonNullable<T> = T extends null | undefined ? never : T;
type C = MyNonNullable<string | null>; // string

type MyExtract<T, U> = T extends U ? T : never;
type D = MyExtract<"a" | "b" | "c", "a" | "c">; // "a" | "c"

type MyExclude<T, U> = T extends U ? never : T;
type E = MyExclude<"a" | "b" | "c", "a">; // "b" | "c"

Union types in conditional types distribute automaticallyT extends U ? X : Y evaluates separately for each member of the union, then merges the results.

6. Mapped Types

// 映射类型:遍历已有类型的键来创建新类型
type MyReadonly<T> = {
    readonly [K in keyof T]: T[K];
};

type MyPartial<T> = {
    [K in keyof T]?: T[K];
};

// 自定义映射类型——将所有属性值变为 Promise
type Async<T> = {
    [K in keyof T]: Promise<T[K]>;
};

interface SyncAPI {
    getUser(): User;
    getAge(): number;
}
type AsyncAPI = Async<SyncAPI>;
// { getUser(): Promise<User>; getAge(): Promise<number> }

// 键的重映射(as 子句)
type Getters<T> = {
    [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};
type UserGetters = Getters<User>;
// { getId: () => number; getName: () => string; ... }

7. Template Literal Types

// 模板字面量类型——在类型层面拼接字符串
type EventName = `on${string}`;
const e1: EventName = "onClick";   // ✓
// const e2: EventName = "click";  // ✗ 缺少 "on" 前缀

// 结合联合类型,生成所有组合
type Color = "red" | "blue";
type Size = "small" | "large";
type Style = `${Color}-${Size}`;
// "red-small" | "red-large" | "blue-small" | "blue-large"

// 内置字符串操作类型
type Upper = Uppercase<"hello">;     // "HELLO"
type Lower = Lowercase<"HELLO">;     // "hello"
type Cap = Capitalize<"hello">;      // "Hello"
type Uncap = Uncapitalize<"Hello">;  // "hello"

8. The infer Keyword

infer declares a type variable to be inferred within conditional types, letting you extract sub-types from complex types:

// 提取函数返回类型(ReturnType 的实现原理)
type MyReturnType<T> = T extends (...args: any[]) => infer R ? R : never;

type R1 = MyReturnType<() => string>;        // string
type R2 = MyReturnType<(x: number) => void>; // void

// 提取函数参数类型
type MyParameters<T> = T extends (...args: infer P) => any ? P : never;
type P1 = MyParameters<(a: string, b: number) => void>; // [string, number]

// 提取 Promise 包裹的类型
type UnwrapPromise<T> = T extends Promise<infer U> ? U : T;
type V1 = UnwrapPromise<Promise<string>>;  // string
type V2 = UnwrapPromise<number>;            // number

// 提取数组元素类型
type ElementOf<T> = T extends (infer E)[] ? E : never;
type El = ElementOf<string[]>; // string

📝 Chapter Summary