← 返回目录

第 2.4 章:泛型与高级类型

TypeScript 类型体操的核心武器

🆚 与 Java 泛型的区别:Java 泛型有类型擦除,运行时泛型信息丢失。TS 的类型系统完全在编译时,但功能远比 Java 泛型强大(条件类型、映射类型等在 Java 中无对应物)。

1. 泛型函数

泛型允许你编写可复用的、类型安全的组件。类型参数用尖括号声明,调用时可显式指定或由编译器自动推断:

// 基本泛型函数
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];

注意箭头函数中 <T,> 的逗号——在 .tsx 文件中需要这个逗号来避免与 JSX 标签混淆。

2. 泛型接口与类

// 泛型接口
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. 泛型约束

使用 extends 关键字约束类型参数,确保泛型满足某些条件:

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. 常用 Utility Types

TypeScript 内置了大量实用工具类型,掌握它们可以大幅减少样板代码:

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. 条件类型

条件类型让你在类型层面做 if/else 判断,语法类似三元表达式:

// 基本语法: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"

联合类型在条件类型中会自动分发——T extends U ? X : Y 会对联合中的每个成员分别求值再合并结果。

6. 映射类型

// 映射类型:遍历已有类型的键来创建新类型
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. 模板字面量类型

// 模板字面量类型——在类型层面拼接字符串
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. infer 关键字

infer 在条件类型中声明一个待推断的类型变量,让你从复杂类型中提取子类型:

// 提取函数返回类型(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

📝 本章要点