TypeScript 类型体操入门:写出会"思考"的类型

举报
yd_231768394 发表于 2026/10/31 22:35:18 2026/10/31
【摘要】 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)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。