Develop

TypeScript 高级类型实战:泛型、条件类型与类型体操

✎ -- 字 🕐 -- 分钟
字号
TypeScript 类型层级1. unknown — 顶层类型(所有类型的父类型)2. {} | object — 非原始类型3. Object / Number / String / Boolean / Symbol / BigInt 包装类型4. number / string / boolean / symbol / bigint 原始类型↓ 箭头方向 = 可赋值方向

前言

TypeScript 的类型系统是其区别于 JavaScript 的核心竞争力。但很多开发者停留在 interface 和基础的 type 别名上,没有真正发挥类型系统的威力。本文从实战出发,带你深入 TypeScript 的高级类型。

一、泛型约束(Generic Constraints)

泛型本身很灵活,但过于灵活反而失去意义。通过 extends 关键字施加约束,让泛型既灵活又安全。

// ❌ 太宽松:T 可以是任何类型
function getLength<T>(arg: T): number {
  // return arg.length; // Error: T 上不存在 length
}

// ✅ 约束 T 必须包含 length 属性
function getLength<T extends { length: number }>(arg: T): number {
  return arg.length; // OK
}

getLength("hello");  // 5
getLength([1, 2, 3]); // 3
// getLength(123);    // Error: number 没有 length

// 进阶:泛型约束 + keyof
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key];
}

const user = { name: 'Alice', age: 30 };
getProperty(user, 'name'); // 'Alice',类型推导为 string
// getProperty(user, 'email'); // Error: 'email' 不存在于 user 类型

二、条件类型(Conditional Types)

条件类型是 TypeScript 中最强大的类型操作符,语法类似三元表达式:T extends U ? X : Y

// 基础条件类型
type IsString<T> = T extends string ? true : false;

type A = IsString<'hello'>; // true
type B = IsString<number>;   // false

// 实战:提取函数返回类型(简化版 ReturnType)
type MyReturnType<T> = T extends (...args: any[]) => infer R ? R : never;

type FnReturn = MyReturnType<() => Promise<string>>; // Promise<string>

// 递归条件类型 — 展平深层嵌套数组
type Flatten<T> = T extends Array<infer U> ? Flatten<U> : T;

type Deep = Flatten<string[][][]>; // string
type Shallow = Flatten<number[]>;   // number

三、映射类型(Mapped Types)

映射类型让你基于已有类型创建新类型,是类型体操的核心工具。

// 将所有属性变为可选
type Partial<T> = { [K in keyof T]?: T[K] };

// 将所有属性变为只读
type Readonly<T> = { readonly [K in keyof T]: T[K] };

// 实战:创建一个 API 响应包装类型
type ApiResponse<T> = {
  [K in keyof T]: {
    value: T[K];
    updatedAt: Date;
    source: 'cache' | 'server';
  };
};

interface User {
  name: string;
  age: number;
}

type CachedUser = ApiResponse<User>;
// { name: { value: string; updatedAt: Date; source: ... }, age: { ... } }

// 高级技巧:条件映射 — 只保留特定类型属性
type PickByType<T, V> = {
  [K in keyof T as T[K] extends V ? K : never]: T[K];
};

interface Mixed {
  id: number;
  name: string;
  active: boolean;
  count: number;
}

type StringProps = PickByType<Mixed, string>;  // { name: string }
type NumberProps = PickByType<Mixed, number>;  // { id: number; count: number }

四、模板字面量类型(Template Literal Types)

TypeScript 4.1+ 引入的模板字面量类型,让字符串类型也具备了编程能力。

// CSS 单位约束
type CSSValue = `${number}${'px' | 'rem' | 'em' | '%' | 'vh' | 'vw'}`;

const width: CSSValue = '100px';  // ✅
const height: CSSValue = '50vh';  // ✅
// const size: CSSValue = 'auto'; // ❌ Error

// 事件类型约束
type EventName = 'click' | 'focus' | 'blur' | 'keydown';
type HandlerName = `on${Capitalize<EventName>}`;
// 'onClick' | 'onFocus' | 'onBlur' | 'onKeydown'

// 实战:类型安全的路由系统
type Route = '/' | '/about' | `/users/${string}` | `/posts/${number}`;

function navigate(route: Route) { /* ... */ }

navigate('/');           // ✅
navigate('/users/alice'); // ✅
navigate('/posts/42');    // ✅
// navigate('/invalid');  // ❌ Error

// 进阶:递归模板类型 — 路径解析
type SplitPath<S extends string> =
  S extends `${infer Head}/${infer Tail}`
    ? Head | SplitPath<Tail>
    : S;

type PathSegments = SplitPath<'api/v1/users/profile'>;
// 'api' | 'v1' | 'users' | 'profile'

五、实战:类型安全的 API 客户端

综合运用以上技术,构建一个完全类型安全的 API 客户端:

// 定义 API 端点和响应类型
interface ApiEndpoints {
  '/users': { id: number; name: string }[];
  '/users/{id}': { id: number; name: string; email: string };
  '/posts': { id: number; title: string }[];
  '/posts/{id}': { id: number; title: string; content: string };
}

// 类型安全的请求函数
function request<P extends keyof ApiEndpoints>(
  path: P,
  params?: Record<string, string>
): Promise<ApiEndpoints[P]> {
  const url = params
    ? path.replace(/\{(\w+)\}/g, (_, k) => params[k])
    : path;
  return fetch(url).then(r => r.json());
}

// 使用 — 完全类型推导
const users = await request('/users');
// 类型: { id: number; name: string }[]

const user = await request('/users/{id}', { id: '42' });
// 类型: { id: number; name: string; email: string }

// ❌ 编译时就会报错
// const invalid = await request('/invalid');
// const wrong = await request('/users/{id}', { wrong: '42' });

总结

技术语法核心用途
泛型约束T extends U限制类型参数范围,获取智能提示
条件类型T extends U ? X : Y根据类型条件分发不同类型
映射类型[K in keyof T]遍历属性批量变换类型结构
模板字面量${prefix}${Suffix}字符串级别的类型约束与构造
infer 推断infer R在条件类型中提取子类型

最佳实践:不要为了炫技而写类型体操。好的类型设计应该让 IDE 自动补全更精准、让重构更安全、让代码成为自己的文档。如果类型比业务逻辑还复杂,就该重新审视设计了。