TypeScript 高级类型体操:你真的会用泛型吗?
TypeScript 的类型系统是图灵完备的——这意味着你可以在类型层面进行编程。掌握高级类型技巧,能让你的代码类型安全性提升一个档次。
泛型基础回顾
泛型允许我们编写可复用的类型安全代码:
// 没有泛型:重复代码
function getFirstNumber(arr: number[]): number | undefined {
return arr[0];
}
function getFirstString(arr: string[]): string | undefined {
return arr[0];
}
// 使用泛型:一次定义,多处使用
function getFirst<T>(arr: T[]): T | undefined {
return arr[0];
}
const num = getFirst([1, 2, 3]); // number | undefined
const str = getFirst(["a", "b"]); // string | undefined
泛型约束(Constraints)
使用 extends 关键字约束泛型参数:
interface HasLength {
length: number;
}
function logLength<T extends HasLength>(item: T): T {
console.log(item.length);
return item;
}
logLength("hello"); // ✅ string 有 length
logLength([1, 2, 3]); // ✅ array 有 length
// logLength(123); // ❌ number 没有 length
条件类型(Conditional Types)
type IsString<T> = T extends string ? true : false;
type A = IsString<"hello">; // true
type B = IsString<42>; // false
// 实用例子:提取 Promise 的返回值类型
type Awaited<T> = T extends Promise<infer U> ? U : T;
type Result = Awaited<Promise<string>>; // string
type Plain = Awaited<number>; // number
infer 关键字
infer 用于在条件类型中推断类型:
// 提取数组元素类型
type ArrayItem<T> = T extends (infer U)[] ? U : never;
type Item = ArrayItem<string[]>; // string
// 提取函数返回值类型
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
type Fn = (x: number) => string;
type Ret = ReturnType<Fn>; // string
// 提取函数第一个参数类型
type FirstParam<T> = T extends (first: infer F, ...rest: any[]) => any ? F : never;
type Param = FirstParam<(a: string, b: number) => void>; // string
映射类型(Mapped Types)
// 将所有属性变为只读
type MyReadonly<T> = {
readonly [K in keyof T]: T[K];
};
// 将所有属性变为可选
type MyPartial<T> = {
[K in keyof T]?: T[K];
};
// 挑选特定属性
type MyPick<T, K extends keyof T> = {
[P in K]: T[P];
};
interface User {
name: string;
age: number;
email: string;
}
type ReadonlyUser = MyReadonly<User>;
type OptionalUser = MyPartial<User>;
type UserPreview = MyPick<User, "name" | "email">;
模板字面量类型(Template Literal Types)
TypeScript 4.1 引入了模板字面量类型,可以进行字符串级别的类型运算:
type EventName<T extends string> = `on${Capitalize<T>}`;
type Click = EventName<"click">; // "onClick"
// 实用例子:类型安全的事件系统
type Events = {
click: { x: number; y: number };
focus: { target: string };
blur: void;
};
type EventHandlerMap = {
[K in keyof Events as `on${Capitalize<K & string>}`]: (e: Events[K]) => void;
};
// EventHandlerMap = {
// onClick: (e: { x: number; y: number }) => void;
// onFocus: (e: { target: string }) => void;
// onBlur: (e: void) => void;
// }
递归类型
// 深度 Partial
type DeepPartial<T> = {
[K in keyof T]?: T[K] extends object ? DeepPartial<T[K]> : T[K];
};
// 深度 Readonly
type DeepReadonly<T> = {
readonly [K in keyof T]: T[K] extends object ? DeepReadonly<T[K]> : T[K];
};
interface Config {
server: {
host: string;
port: number;
ssl: {
enabled: boolean;
cert: string;
};
};
}
type PartialConfig = DeepPartial<Config>;
// 所有层级都变为可选
实用工具类型实现
// 排除 null 和 undefined
type NonNullable<T> = T extends null | undefined ? never : T;
// 从联合类型中排除特定类型
type MyExclude<T, U> = T extends U ? never : T;
type OnlyString = MyExclude<string | number | boolean, number | boolean>; // string
// 提取联合类型中的特定类型
type MyExtract<T, U> = T extends U ? T : never;
type OnlyNum = MyExtract<string | number | boolean, number>; // number
// 函数参数类型
type Parameters<T extends (...args: any) => any> =
T extends (...args: infer P) => any ? P : never;
// 构造函数参数类型
type ConstructorParameters<T extends abstract new (...args: any) => any> =
T extends abstract new (...args: infer P) => any ? P : never;
实战:类型安全的 API 客户端
// 定义 API 路由
interface ApiRoutes {
"/users": {
get: { response: User[] };
post: { body: CreateUserDto; response: User };
};
"/users/:id": {
get: { params: { id: string }; response: User };
put: { params: { id: string }; body: UpdateUserDto; response: User };
delete: { params: { id: string }; response: void };
};
}
// 类型安全的请求函数
type RequestConfig<Method extends string, Config> =
Config extends { body: infer B }
? { method: Method; body: B } & Omit<Config, "body" | "response">
: { method: Method } & Omit<Config, "response">;
function request<
Path extends keyof ApiRoutes,
Method extends keyof ApiRoutes[Path]
>(
path: Path,
config: RequestConfig<Method & string, ApiRoutes[Path][Method]>
): Promise<ApiRoutes[Path][Method] extends { response: infer R } ? R : never> {
// 实现省略...
return fetch(path, config as any).then(r => r.json());
}
// 使用时有完整的类型提示:
const users = await request("/users", { method: "get" });
// users: User[]
const newUser = await request("/users", {
method: "post",
body: { name: "Alice", email: "alice@example.com" }
});
// newUser: User
总结
TypeScript 的高级类型工具让你能在编译时捕获更多错误。关键技巧包括:
- 泛型约束限制类型参数的范围
- 条件类型实现类型的条件分支
infer提取类型信息- 映射类型对对象类型进行变换
- 模板字面量类型实现字符串级别的类型安全
掌握这些技巧,你写出的 TypeScript 代码将更加健壮和自文档化。