Z
ZHANK
接口与类

泛型进阶

掌握泛型约束 extends、keyof 与映射类型

泛型进阶

前面学的泛型解决的是"函数能接受不同类型"的问题。这一章解决的是"如何从已有类型推导、变换出新类型"——这才是 TypeScript 类型系统的真正威力。

学完本章你将: 掌握 keyof、映射类型、条件类型、infer。


keyof —— 获取对象所有键

keyof T 返回 T 所有属性名的联合类型。它的经典用法是做类型安全的取值函数

typescript
interface User {
    name: string;
    age: number;
}

type UserKeys = keyof User; // "name" | "age"

// 这个函数保证 key 一定是 User 的真实属性名
function getValue<T, K extends keyof T>(obj: T, key: K): T[K] {
    return obj[key];
}

const user: User = { name: "Alice", age: 25 };
getValue(user, "name");  // ✅ 返回 string,有智能提示
// getValue(user, "email"); // ❌ "email" 不存在于 User

💡 为什么不用 string 类型? getValue(user, "任意字符串") 可能传入不存在的属性名,运行时会返回 undefined。K extends keyof T 让 TypeScript 在编译时就发现错误。


映射类型 —— 批量修改属性

映射类型遍历一个类型的所有属性,逐个修改它们的修饰符。就像对每个属性说"把你们都变成 readonly"。

typescript
// 动手实现内置的 Readonly
type MyReadonly<T> = {
    readonly [K in keyof T]: T[K];  // 遍历 T 的每个属性,加 readonly
};

type User = { name: string; age: number };
type ReadonlyUser = MyReadonly<User>;
// { readonly name: string; readonly age: number }

[K in keyof T] 可以读作:"对于 T 的每一个属性键 K,做后面的操作"。这是 TypeScript 中最强大的类型变换语法。


条件类型 —— 类型的 if-else

typescript
// 语法:T extends U ? X : Y
// 如果 T 是 U 的子类型,返回 X,否则返回 Y

type IsArray<T> = T extends unknown[] ? "是数组" : "不是数组";
type A = IsArray<string[]>; // "是数组"
type B = IsArray<number>;   // "不是数组"

分布式条件类型: 当 T 是联合类型时,条件会分布到每个成员

typescript
type ToArray<T> = T extends unknown ? T[] : never;
// 注意:T = string | number 时
type R = ToArray<string | number>; // string[] | number[]
// 等价于 ToArray<string> | ToArray<number>

infer —— 从类型中提取

infer 在条件类型中声明一个变量,让 TypeScript 推断出那个位置的实际类型。

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

// 提取函数返回值(这就是内置 ReturnType 的实现)
type MyReturnType<T> = T extends (...args: unknown[]) => infer R ? R : never;
type R = MyReturnType<() => Promise<number>>; // Promise<number>

// 提取 Promise 包裹的值(递归)
type Awaited<T> = T extends Promise<infer V> ? Awaited<V> : T;
type V = Awaited<Promise<Promise<string>>>; // string(递归解开)

💡 infer 只能用在条件类型的 extends 子句中。 它"就地声明,就地使用"——不像 T 需要预先在 <> 里声明。


infer 关键字

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

// 提取 Promise 值类型
type Awaited<T> = T extends Promise<infer V> ? V : T;
type Result = Awaited<Promise<number>>; // number

// 提取函数参数类型
type FirstParam<T> = T extends (first: infer P, ...rest: unknown[]) => unknown ? P : never;
type Param = FirstParam<(a: string, b: number) => void>; // string