TypeScript 类型体操入门:写出会"思考"的类型
【摘要】 TypeScript 类型体操入门:写出会"思考"的类型 前言TypeScript 的类型系统是一门"图灵完备的小型语言"。学会基础泛型之后,再进一步就能写出自动推导、自动校验的类型逻辑——也就是常说的"类型体操"。本文用最小篇幅讲清核心积木。 一、四个核心工具类型 1. keyof:取键的联合interface User { id: number; name: string;}typ...
TypeScript 类型体操入门:写出会"思考"的类型
前言
TypeScript 的类型系统是一门"图灵完备的小型语言"。学会基础泛型之后,再进一步就能写出自动推导、自动校验的类型逻辑——也就是常说的"类型体操"。本文用最小篇幅讲清核心积木。
一、四个核心工具类型
1. keyof:取键的联合
interface User {
id: number;
name: string;
}
type UserKeys = keyof User; // 'id' | 'name'
2. typeof:从值取类型
const config = { host: 'localhost', port: 3306 };
type Config = typeof config; // { host: string; port: number }
3. 索引访问:从类型中"挖"出子类型
type UserList = User[];
type ItemType = UserList[number]; // User
type NameType = User['name']; // string
4. 条件类型 + infer:类型的 if 语句
type Unwrap<T> = T extends Promise<infer U> ? U : T;
type A = Unwrap<Promise<string>>; // string
type B = Unwrap<number>; // number
infer 表示"在匹配过程中捕获一个类型变量",是类型体操最强大的关键字。
二、映射类型:批量变换
type Readonly<T> = {
readonly [K in keyof T]: T[K];
};
type PartialUser = Partial<User>; // 所有字段变可选
映射类型 + 条件类型的组合可以做出"按键过滤":
type PickByType<T, U> = {
[K in keyof T as T[K] extends U ? K : never]: T[K];
};
type StringFields = PickByType<User, string>; // { name: string }
as 重映射把不需要的键映射成 never,从而"删掉"它们。
三、实战场景
场景 1:类型安全的事件系统
type Events = {
login: { userId: string };
logout: undefined;
};
class Emitter<E extends Record<string, unknown>> {
private handlers: { [K in keyof E]?: ((p: E[K]) => void)[] } = {};
on<K extends keyof E>(event: K, handler: (payload: E[K]) => void) {
(this.handlers[event] ??= []).push(handler);
}
emit<K extends keyof E>(event: K, payload: E[K]) {
this.handlers[event]?.forEach((h) => h(payload));
}
}
const emitter = new Emitter<Events>();
emitter.on('login', (p) => console.log(p.userId)); // p 自动推导为 { userId: string }
emitter.emit('login', { userId: 'u1' });
// emitter.emit('login', {}); // ❌ 编译报错
// emitter.emit('register', {}); // ❌ 事件名不存在
场景 2:从路由表自动推导 API 类型
const routes = {
'GET /users': { params: { page: number }, res: User[] },
'GET /users/:id': { params: { id: string }, res: User },
} as const;
type Route = keyof typeof routes;
async function request<R extends Route>(
route: R,
...args: typeof routes[R]['params'] extends undefined
? []
: [body: typeof routes[R]['params']]
): Promise<typeof routes[R]['res']> {
return fetch(route as string).then((r) => r.json());
}
const users = await request('GET /users', { page: 1 }); // User[]
接口定义改一处,调用方全部自动跟随,这就是"会思考"的类型。
四、什么时候该克制
类型体操的目标是减少重复、提前暴露错误,不是炫技:
- 递归类型超过两层、报错信息变成一大坨红字时,宁可写
any收窄或拆小步骤; - 类型逻辑复杂度应与业务收益成正比,核心数据流(API、状态树)值得精雕,普通工具函数不必;
- 给复杂类型留一行注释,半年后的自己会感谢你。
总结
类型体操的三块积木:条件类型做判断,映射类型做变换,infer 做捕获。掌握后,你会发现大量运行时校验和重复定义可以下沉到类型层——编译期就消灭一整类 bug。
【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱:
cloudbbs@huaweicloud.com
- 点赞
- 收藏
- 关注作者
评论(0)