一、泛型(Generics)
1. 概念
泛型是把类型参数化,让函数/类/接口在保持类型安全的同时变得可复用。常见场景:容器类型、工具函数、库接口、组件 Props。
2. 基本语法
function identity<T>(x: T): T {
return x;
}
const s = identity<string>('hello'); // s: string
const n = identity(123); // 泛型可被推断,n: number
3. 常见用法
- 函数泛型
- 接口/类型别名泛型
- 类泛型
- 泛型约束(
extends) - 默认类型参数
示例:泛型约束与 keyof
function pluck<T, K extends keyof T>(obj: T, keys: K[]): T[K][] {
return keys.map(k => obj[k]);
}
const user = { id: 1, name: 'Alice' };
const names = pluck(user, ['name']); // 类型安全:T[K] 推断为 string
4. 泛型高级点
- 约束(constraints) :保证泛型参数满足某些结构(比如有某个属性)。
- 默认泛型参数:
type Box<T = string> = { value: T } - 联合/交叉与泛型:理解泛型和联合/交叉结合时的推断行为。
- 推断优先级:当有多个泛型参数时,类型推断的传播规则(有时需显式指定)。
- 泛型与函数重载/JSX:组件泛型在 React/TSX 下的 tricky 点(常在面试被问)。
5. 常见陷阱
- 不要滥用
any——用unknown更安全。 - 泛型过宽会丢失具体信息;过窄又失去泛型收益。
- 不同泛型参数间的关系要明确(例如
T与K的约束)。
二、映射类型(Mapped Types)
1. 概念
映射类型是基于现有类型的键集合,生成一个新类型。TypeScript 的内置工具类型(如 Partial<T>、Readonly<T>、Record<K,T>)都是映射类型或基于映射类型实现的。
2. 基本语法
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};
type Partial<T> = {
[P in keyof T]?: T[P];
};
[P in keyof T] 遍历 T 的所有键,构造新的属性映射。
3. 常见内置工具类型
Partial<T>:把所有属性变成可选Required<T>:把所有属性变成必需Readonly<T>:把所有属性变为 readonlyPick<T, K>:选择子集属性Omit<T, K>:排除某些属性(通常通过Pick或条件类型实现)Record<K extends keyof any, T>:从键到值的映射
示例:实现 Omit
type MyOmit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;
// 或用映射类型的 key remapping(TS 4.1+):
type MyOmit2<T, K extends keyof any> = {
[P in keyof T as P extends K ? never : P]: T[P]
};
4. Key Remapping(TS 4.1+)
可以在映射时重写键名或过滤键:
type Prefixed<T, Prefix extends string> = {
[K in keyof T as `${Prefix}${string & K}`]: T[K]
};
type A = { id: number; name: string };
type B = Prefixed<A, 'x_'>; // { x_id: number; x_name: string }
5. 严格/可选/readonly 修饰
映射可以结合 + / - 操作符改变修饰符:
type Mutable<T> = { -readonly [P in keyof T]: T[P] };
type Optional<T> = { [P in keyof T]?: T[P] };
三、条件类型(Conditional Types)
1. 概念
条件类型类似类型级别的 if,语法:
T extends U ? X : Y
如果 T 可赋值给 U,条件类型取 X,否则取 Y。
2. 基本示例
type IsString<T> = T extends string ? true : false;
type A = IsString<'a'>; // true
type B = IsString<number>; // false
3. 分布式条件类型(distributive conditional types)
当条件类型的左侧是一个裸的类型参数 T(不是被包裹在数组或元组等),并且 T 是联合类型时,条件类型会对联合的每个成员分别分发,再合并结果(即“分布式”)。
type Foo<T> = T extends string ? 'S' : 'N';
type R = Foo<'a' | 1>;
// 等价于 Foo<'a'> | Foo<1> -> 'S' | 'N'
避免分发:将 T 包在元组中即可阻止分发:
type NoDistrib<T> = [T] extends [string] ? 'S' : 'N';
4. 用 infer 做类型推断
在条件类型里用 infer 从某种复合类型中提取子类型:
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
type F = (x: number) => string;
type R = ReturnType<F>; // string
更多示例:数组元素类型、Promise 解包(Flatten)
type ElementType<T> = T extends (infer U)[] ? U : T;
type E = ElementType<number[]>; // number
type UnwrapPromise<T> = T extends Promise<infer U> ? U : T;
5. 常见组合用法(映射 + 条件 + infer)
实现深度可选(DeepPartial):
type DeepPartial<T> = {
[P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P]
};
注意:这里 T[P] extends object 会匹配数组和函数,通常要更精确:
type IsPlainObject<T> = T extends object
? T extends Function ? false : true
: false;
type DeepPartial2<T> = {
[P in keyof T]?: IsPlainObject<T[P]> extends true ? DeepPartial2<T[P]> : T[P];
};
6. 常用工具类型底层实现
-
Exclude<T, U>:剔除联合中的成员type MyExclude<T, U> = T extends U ? never : T; -
Extract<T, U>:提取交集成员type MyExtract<T, U> = T extends U ? T : never; -
NonNullable<T>:去除 null|undefinedtype MyNonNullable<T> = T extends null | undefined ? never : T;
四、三者结合
“泛型是类型参数化,用于增强复用性和类型安全。
映射类型通过
[P in keyof T]遍历属性可以生成Partial/Readonly这类类型;
条件类型
T extends U ? X : Y可以做类型分支,注意当左侧是联合时条件会分发(distributive),并且可以用infer从复杂类型中提取子类型。
工程中常把三者结合用于 DTO -> Form、API -> Client 类型转换等。
场景:从 API 响应类型生成表单值类型(把所有属性变为可选,数组不展开,函数去掉)
type ApiType = {
id: number;
name: string;
tags: string[];
meta?: { createdAt: string };
onClick?: () => void;
};
type Formify<T> = {
[K in keyof T]?: T[K] extends (...args: any[]) => any // 条件类型过滤函数
? never
: T[K] extends Array<infer U> // 数组处理
? Array<Formify<U>>
: T[K] extends object
? Formify<T[K]>
: T[K];
};
// 结果:{ id?: number; name?: string; tags?: string[]; meta?: { createdAt?: string } }
type FormType = Formify<ApiType>;
这个例子展示了:
- 映射类型遍历属性
- 条件类型判断属性类别并用
infer提取数组元素类型 - 递归地把嵌套对象也转换
- 实现
Partial<T>/Readonly<T>/Pick<T, K>/Omit<T, K>
type MyPartial<T> = {
[P in keyof T]?: T[P];
};
type MyReadonly<T> = {
readonly [P in keyof T]: T[P];
};
type MyPick<T, K extends keyof T> = {
[P in K]: T[P];
};
type MyOmit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;
- 写
ReturnType<F>(使用infer)
type MyReturnType<F> =
F extends (...args: any[]) => infer R
? R
: never;
- 解释
T extends U ? X : Y在T = A | B时的结果 示例:
T extends U ? X : Y
当 T 是一个联合类型:
type R = ('a' | 1) extends string ? 'YES' : 'NO';
这个表达式 会被分发:
相当于:
('a' extends string ? 'YES' : 'NO')
|
(1 extends string ? 'YES' : 'NO')
结果:
type R = 'YES' | 'NO';
- 如何阻止分发
把 T 放在元组中:
type NoDistrib<T> = [T] extends [string] ? 'YES' : 'NO';
type A = NoDistrib<'a' | 1>; // NO(整体比较,不会分发)