TypeScript 高级类型体操:实际应用场景

写 TypeScript 三年,从 any 到高级类型,记录实际应用场景。

实用工具类型

DeepPartial

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

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

const update: DeepPartial<User> = {
  profile: { age: 25 },  // 不需要提供所有字段
};

DeepRequired

type DeepRequired<T> = T extends object
  ? { [P in keyof T]-?: DeepRequired<T[P]> }
  : T;

DeepReadonly

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

const config: DeepReadonly<Config> = { ... };
config.api.url = 'new-url';  // 报错!

RequireKeys

type RequireKeys<T, K extends keyof T> = T & Required<Pick<T, K>>;

interface User {
  id?: number;
  name?: string;
  email?: string;
}

type UserWithEmail = RequireKeys<User, 'email'>;
// email 必填,其他可选

条件类型实战

API 响应类型

type ApiResponse<T> = T extends { list: infer L }
  ? { data: L[]; total: number }
  : { data: T };

// 使用
interface UserListResponse {
  list: User;
  total: number;
}

type Response = ApiResponse<UserListResponse>;
// { data: User[]; total: number }

type SingleResponse = ApiResponse<User>;
// { data: User }

函数参数提取

type ExtractParameters<T> = T extends (...args: infer P) => any ? P : never;

function createUser(name: string, age: number, email: string) {
  return { name, age, email };
}

type CreateUserParams = ExtractParameters<typeof createUser>;
// [string, number, string]

Promise 值提取

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

type Result = Awaited<Promise<Promise<string>>>;
// string

模板字面量类型

路由类型

type Route = '/users' | '/users/:id' | '/posts' | '/posts/:postId/comments/:commentId';

type ExtractParams<T extends string> = T extends `${string}:${infer Param}/${infer Rest}`
  ? Param | ExtractParams<`/${Rest}`>
  : T extends `${string}:${infer Param}`
  ? Param
  : never;

type Params = ExtractParams<Route>;
// 'id' | 'postId' | 'commentId'

事件名称生成

type Events = {
  user: ['created', 'updated', 'deleted'];
  post: ['created', 'published'];
};

type EventName<T extends keyof Events> = `${T}:${Events[T][number]}`;

type UserEvent = EventName<'user'>;
// 'user:created' | 'user:updated' | 'user:deleted'

映射类型实战

表单字段生成

interface Entity {
  id: number;
  name: string;
  email: string;
}

type FormField<T> = {
  [K in keyof T]: {
    value: T[K];
    error?: string;
    touched: boolean;
  };
};

type EntityForm = FormField<Entity>;
// {
//   id: { value: number; error?: string; touched: boolean };
//   name: { value: string; error?: string; touched: boolean };
//   email: { value: string; error?: string; touched: boolean };
// }

API 参数类型

type QueryParams<T> = {
  [K in keyof T]?: T[K] extends string
    ? T[K]
    : T[K] extends number
    ? number | string
    : T[K];
};

interface Filter {
  name: string;
  age: number;
  status: 'active' | 'inactive';
}

type Params = QueryParams<Filter>;
// {
//   name?: string;
//   age?: number | string;  // 支持 "18"
//   status?: 'active' | 'inactive';
// }

类型体操

类型守卫

自定义类型守卫

function isUser(value: unknown): value is User {
  return (
    typeof value === 'object' &&
    value !== null &&
    'id' in value &&
    'name' in value &&
    'email' in value
  );
}

// 使用
if (isUser(data)) {
  console.log(data.name);  // 类型正确
}

断言函数

function assert(condition: unknown, message: string): asserts condition {
  if (!condition) {
    throw new Error(message);
  }
}

function process(value: unknown) {
  assert(typeof value === 'string', 'Value must be a string');
  console.log(value.toUpperCase());  // 类型正确
}

类型体操 vs 实用性

场景推荐程度
公共库类型定义
项目基础类型
业务逻辑类型
一次性脚本

不要过度:

// 过度:太复杂,维护困难
type DeepNestedCondition<T> = T extends object
  ? T extends Array<infer U>
    ? DeepNestedCondition<U>[]
    : { [K in keyof T]: DeepNestedCondition<T[K]> }
  : T;

// 适度:简单实用
type SafeDeepPartial<T> = {
  [P in keyof T]?: T[P] extends object ? SafeDeepPartial<T[P]> : T[P];
};

调试类型

类型显示

type ShowType<T> = { [K in keyof T]: T[K] };

// 鼠标悬停查看类型
type Result = ShowType<ComplexType>;

类型断言调试

type Debug<T> = T extends infer U ? U : never;

// 强制展开类型
type Expanded = Debug<SomeComplexGenericType>;

总结

类型体操是工具,不是目的。

使用原则:

  1. 解决实际问题
  2. 代码可读性优先
  3. 必要时加注释
  4. 复杂类型单独文件

好的类型设计让代码更安全、IDE 提示更友好。