← 返回文章列表 / 编程语言

TypeScript 类型体操实战:编写可维护的工程级工具类型

深入理解 TypeScript 的条件类型、infer 关键字与模板字面量类型,通过真实业务场景掌握既安全又易读的类型推导技巧。

在实际项目开发中,良好的类型设计不仅能在编译期捕获潜在 Bug,还能通过 IDE 智能提示大幅提升团队协作效率。今天我们来梳理几个在工程实践中高频使用的进阶类型模式。

1. 深度只读 (DeepReadonly)

内置的 Readonly<T> 只能处理对象的第一层属性。当我们需要保护全局配置或不可变状态树时,可以通过递归条件类型实现 DeepReadonly:

type DeepReadonly<T> = {
  readonly [P in keyof T]: T[P] extends object
    ? T[P] extends Function
      ? T[P]
      : DeepReadonly<T[P]>
    : T[P];
};

interface AppConfig {
  theme: {
    mode: 'light' | 'dark';
    accentColor: string;
  };
  features: string[];
}

const config: DeepReadonly<AppConfig> = {
  theme: { mode: 'dark', accentColor: '#10b981' },
  features: ['search', 'rss'],
};

// 编译期报错:Cannot assign to 'mode' because it is a read-only property.
// config.theme.mode = 'light';

2. 使用 infer 提取异步函数返回值

在封装 API 请求层或状态管理 Hook 时,我们经常需要从已有的异步函数中提取其解析后的数据结构:

type UnwrapPromise<T> = T extends Promise<infer U> ? UnwrapPromise<U> : T;

type AsyncReturnType<T extends (...args: any[]) => any> = UnwrapPromise<ReturnType<T>>;

async function fetchArticleSummary(slug: string) {
  return {
    slug,
    views: 1024,
    tags: ['TypeScript', 'Astro'] as const,
  };
}

// 自动推导为 { slug: string; views: number; tags: readonly ["TypeScript", "Astro"] }
type ArticleSummary = AsyncReturnType<typeof fetchArticleSummary>;

3. 模板字面量类型与事件总线

TypeScript 4.1 引入的模板字面量类型让我们能够对字符串格式进行精确建模:

模式名称 示例语法 典型应用场景
事件名自动派生 `on${Capitalize<Event>}` 组件 Props 事件回调约束
路由参数解析 `/blog/${infer Slug}` 类型安全的前端路由器
CSS 单位校验 `${number}px` | `${number}rem` 设计系统 Token 类型定义

在编写复杂类型时,应当始终遵循 “可读性优于炫技” 的原则:为复杂的中间类型命名,并配合 JSDoc 注释给出示例。

弗洛洛 · Phrolova 漂泊者……你也是来听演奏的吗?(盯——)
Q版弗洛洛看板娘