目录

第十五章:工具类型

本章概述

TypeScript 提供了一系列内置的工具类型(Utility Types),用于常见的类型转换操作。这些工具类型基于映射类型和条件类型实现,能够帮助我们高效地创建新的类型。本章将详细介绍所有内置工具类型的使用方法,并展示如何创建自定义工具类型。

15.1 基础工具类型

15.1.1 Partial<T> - 可选属性

将所有属性变为可选的。

type Partial<T> = {
  [P in keyof T]?: T[P];
};
 
// 使用示例
interface User {
  id: number;
  name: string;
  email: string;
  age: number;
}
 
// 创建用户时,id 由数据库生成
type CreateUserDTO = Partial<User>;
// {
//   id?: number;
//   name?: string;
//   email?: string;
//   age?: number;
// }
 
// 更精确:只让部分属性可选
type CreateUserInput = Omit<User, "id"> & Partial<Pick<User, "age">>;
// {
//   name: string;
//   email: string;
//   age?: number;
// }
 
// 实际应用:更新函数
function updateUser(user: User, updates: Partial<User>): User {
  return { ...user, ...updates };
}
 
const user: User = { id: 1, name: "Alice", email: "alice@example.com", age: 25 };
const updated = updateUser(user, { age: 26 });  // OK,只更新 age

15.1.2 Required<T> - 必需属性

将所有属性变为必需的(移除可选修饰符)。

type Required<T> = {
  [P in keyof T]-?: T[P];
};
 
// 使用示例
interface Config {
  host: string;
  port?: number;
  ssl?: boolean;
  timeout?: number;
}
 
type StrictConfig = Required<Config>;
// {
//   host: string;
//   port: number;
//   ssl: boolean;
//   timeout: number;
// }
 
// 实际应用:配置合并后的校验
function validateConfig(config: Required<Config>): boolean {
  return config.port > 0 && config.port < 65536 && config.timeout > 0;
}
 
const fullConfig: Required<Config> = {
  host: "localhost",
  port: 3000,
  ssl: false,
  timeout: 5000
};

15.1.3 Readonly<T> - 只读属性

将所有属性变为只读的。

type Readonly<T> = {
  readonly [P in keyof T]: T[P];
};
 
// 使用示例
interface Point {
  x: number;
  y: number;
}
 
type ImmutablePoint = Readonly<Point>;
// {
//   readonly x: number;
//   readonly y: number;
// }
 
const point: ImmutablePoint = { x: 0, y: 0 };
// point.x = 1;  // Error: Cannot assign to 'x' because it is a read-only property
 
// 实际应用:常量配置
const API_CONFIG: Readonly<{
  baseURL: string;
  version: string;
  timeout: number;
}> = {
  baseURL: "https://api.example.com",
  version: "v1",
  timeout: 5000
};
 
// API_CONFIG.timeout = 10000;  // Error

15.2 属性选择工具类型

15.2.1 Pick<T, K> - 选取属性

从类型 T 中选取一组属性 K 组成新类型。

type Pick<T, K extends keyof T> = {
  [P in K]: T[P];
};
 
// 使用示例
interface User {
  id: number;
  name: string;
  email: string;
  password: string;
  createdAt: Date;
  updatedAt: Date;
}
 
// 创建公开的用户信息类型(排除敏感字段)
type PublicUser = Pick<User, "id" | "name" | "email">;
// {
//   id: number;
//   name: string;
//   email: string;
// }
 
// API 响应类型
type UserResponse = Pick<User, "id" | "name" | "email" | "createdAt">;
 
function getPublicUser(user: User): PublicUser {
  return {
    id: user.id,
    name: user.name,
    email: user.email
  };
}

15.2.2 Omit<T, K> - 排除属性

从类型 T 中排除一组属性 K 组成新类型。

type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;
 
// 使用示例
interface User {
  id: number;
  name: string;
  email: string;
  password: string;
  internalNotes: string;
}
 
// 排除敏感和内部字段
type SafeUser = Omit<User, "password" | "internalNotes">;
// {
//   id: number;
//   name: string;
//   email: string;
// }
 
// 创建用户请求(排除自动生成的字段)
type CreateUserRequest = Omit<User, "id">;
 
// 更新用户请求(排除 id 和所有可选处理)
type UpdateUserRequest = Partial<Omit<User, "id">>;
 
// 实际应用:DTO 转换
class UserDTO {
  static toSafeUser(user: User): SafeUser {
    const { password, internalNotes, ...safeUser } = user;
    return safeUser;
  }
}

15.3 联合类型操作工具

15.3.1 Exclude<T, U> - 排除类型

从 T 中排除可以赋值给 U 的类型。

type Exclude<T, U> = T extends U ? never : T;
 
// 使用示例
type AllStatus = "pending" | "success" | "error" | "loading";
type FinalStatus = Exclude<AllStatus, "pending" | "loading">;
// "success" | "error"
 
// 从联合类型中排除 null/undefined
type MaybeString = string | null | undefined;
type DefinitelyString = Exclude<MaybeString, null | undefined>;
// string
 
// 实际应用:状态机转换
type State = 
  | { status: "idle" }
  | { status: "loading"; requestId: string }
  | { status: "success"; data: any }
  | { status: "error"; error: Error };
 
type NonIdleState = Exclude<State, { status: "idle" }>;
// { status: "loading" } | { status: "success" } | { status: "error" }

15.3.2 Extract<T, U> - 提取类型

从 T 中提取可以赋值给 U 的类型。

type Extract<T, U> = T extends U ? T : never;
 
// 使用示例
type AllEvents = "click" | "hover" | "scroll" | "resize" | "keydown";
type MouseEvents = Extract<AllEvents, "click" | "hover">;
// "click" | "hover"
 
// 提取特定类型的属性名
type StringProperties<T> = {
  [K in keyof T]: T[K] extends string ? K : never;
}[keyof T];
 
interface Person {
  name: string;
  age: number;
  email: string;
  isActive: boolean;
}
 
type StringKeys = StringProperties<Person>;
// "name" | "email"

15.3.3 NonNullable<T> - 非空类型

从 T 中排除 null 和 undefined。

type NonNullable<T> = T extends null | undefined ? never : T;
 
// 使用示例
type MaybeNumber = number | null | undefined;
type DefinitelyNumber = NonNullable<MaybeNumber>;
// number
 
// 实际应用:过滤数组中的空值
function filterNonNull<T>(items: (T | null | undefined)[]): T[] {
  return items.filter((item): item is T => item !== null && item !== undefined);
}
 
const maybeNumbers: (number | null)[] = [1, null, 2, null, 3];
const numbers = filterNonNull(maybeNumbers);
// number[]

15.4 函数相关工具类型

15.4.1 Parameters<T> - 提取参数类型

提取函数类型的参数类型,以元组形式返回。

type Parameters<T extends (...args: any) => any> = 
  T extends (...args: infer P) => any ? P : never;
 
// 使用示例
function createUser(name: string, email: string, age?: number): { id: number } {
  return { id: 1 };
}
 
type CreateUserParams = Parameters<typeof createUser>;
// [name: string, email: string, age?: number]
 
// 提取第一个参数
type FirstParameter<T extends (...args: any) => any> = 
  Parameters<T> extends [infer P, ...any[]] ? P : never;
 
// 提取除第一个外的参数
type RestParameters<T extends (...args: any) => any> =
  Parameters<T> extends [any, ...infer P] ? P : never;
 
// 实际应用:高阶函数类型安全
function withLogging<T extends (...args: any[]) => any>(
  fn: T
): (...args: Parameters<T>) => ReturnType<T> {
  return (...args) => {
    console.log("Calling function with:", args);
    return fn(...args);
  };
}
 
const loggedCreateUser = withLogging(createUser);
// 类型: (name: string, email: string, age?: number) => { id: number }

15.4.2 ReturnType<T> - 提取返回类型

提取函数类型的返回类型。

type ReturnType<T extends (...args: any) => any> =
  T extends (...args: any) => infer R ? R : any;
 
// 使用示例
async function fetchUser(id: number) {
  return {
    id,
    name: "Alice",
    email: "alice@example.com"
  };
}
 
type FetchUserReturn = ReturnType<typeof fetchUser>;
// Promise<{ id: number; name: string; email: string; }>
 
// 提取 Promise 的解析类型
type Awaited<T> = T extends Promise<infer R> ? R : T;
type UserData = Awaited<FetchUserReturn>;
// { id: number; name: string; email: string; }
 
// 实际应用:API 客户端类型
class ApiClient {
  async request<T>(url: string): Promise<T> {
    const response = await fetch(url);
    return response.json();
  }
}
 
const client = new ApiClient();
type ApiRequestReturn<T> = ReturnType<ApiClient["request"]> extends Promise<infer R> 
  ? R 
  : never;

15.4.3 ThisParameterType<T> 和 OmitThisParameter<T>

处理函数中的 this 参数。

// 提取 this 参数的类型
type ThisParameterType<T> = T extends (this: infer U, ...args: any[]) => any 
  ? U 
  : unknown;
 
// 移除 this 参数
type OmitThisParameter<T> = unknown extends ThisParameterType<T>
  ? T
  : T extends (this: any, ...args: infer A) => infer R
  ? (...args: A) => R
  : T;
 
// 使用示例
function greet(this: { name: string }, greeting: string): string {
  return `${greeting}, ${this.name}!`;
}
 
type GreetThis = ThisParameterType<typeof greet>;
// { name: string }
 
type GreetWithoutThis = OmitThisParameter<typeof greet>;
// (greeting: string) => string

15.5 构造函数工具类型

15.5.1 ConstructorParameters<T> - 构造参数

提取构造函数类型的参数类型。

type ConstructorParameters<T extends abstract new (...args: any) => any> =
  T extends abstract new (...args: infer P) => any ? P : never;
 
// 使用示例
class User {
  constructor(
    public name: string,
    public age: number,
    public email?: string
  ) {}
}
 
type UserConstructorParams = ConstructorParameters<typeof User>;
// [name: string, age: number, email?: string]
 
// 实际应用:工厂函数
function createUser(...args: ConstructorParameters<typeof User>): User {
  return new User(...args);
}
 
const user = createUser("Alice", 25, "alice@example.com");

15.5.2 InstanceType<T> - 实例类型

提取构造函数类型的实例类型。

type InstanceType<T extends abstract new (...args: any) => any> =
  T extends abstract new (...args: any) => infer R ? R : any;
 
// 使用示例
type UserInstance = InstanceType<typeof User>;
// User
 
// 实际应用:依赖注入容器
type ClassConstructor<T = any> = new (...args: any[]) => T;
 
class Container {
  private instances = new Map<string, any>();
 
  register<T>(key: string, Constructor: ClassConstructor<T>): void {
    this.instances.set(key, new Constructor());
  }
 
  get<T>(key: string): T {
    return this.instances.get(key);
  }
}
 
const container = new Container();
container.register("user", User);
const userService = container.get<User>("user");

15.6 字符串操作工具类型

15.6.1 模板字面量工具类型(TypeScript 4.1+)

// Uppercase - 转大写
type HTTPMethod = "get" | "post";
type UpperMethods = Uppercase<HTTPMethod>;
// "GET" | "POST"
 
// Lowercase - 转小写
type LowerMethods = Lowercase<"GET" | "POST">;
// "get" | "post"
 
// Capitalize - 首字母大写
type EventNames = "click" | "hover";
type HandlerNames = `on${Capitalize<EventNames>}`;
// "onClick" | "onHover"
 
// Uncapitalize - 首字母小写
type PropertyNames = "Name" | "Age";
type CamelCase = `get${PropertyNames}`;
// "getName" | "getAge"

15.6.2 字符串解析类型

// 提取事件名前缀
type EventPrefix<T extends string> = 
  T extends `${infer Prefix}${string}` ? Prefix : never;
 
type ClickPrefix = EventPrefix<"onClick">;  // "on"
 
// 提取 camelCase 的各个部分
type SplitCamelCase<S extends string> = 
  S extends `${infer Head}${infer Tail}`
    ? Head extends Uppercase<Head>
      ? [Head, ...SplitCamelCase<Tail>]
      : [Head, ...SplitCamelCase<Tail>]
    : [];
 
// 字符串路径提取
type ExtractPathParams<T extends string> = 
  T extends `${string}:${infer Param}/${infer Rest}`
    ? { [K in Param]: string } & ExtractPathParams<`/${Rest}`>
    : T extends `${string}:${infer Param}`
    ? { [K in Param]: string }
    : {};
 
type RouteParams = ExtractPathParams<"/users/:id/posts/:postId">;
// { id: string; postId: string; }

15.7 自定义工具类型

15.7.1 深度工具类型

// 深度 Partial
type DeepPartial<T> = {
  [P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];
};
 
// 深度 Required
type DeepRequired<T> = {
  [P in keyof T]-?: T[P] extends object ? DeepRequired<T[P]> : T[P];
};
 
// 深度 Readonly
type DeepReadonly<T> = {
  readonly [P in keyof T]: T[P] extends object ? DeepReadonly<T[P]> : T[P];
};
 
interface Nested {
  a: {
    b: {
      c: string;
    };
  };
}
 
type PartialNested = DeepPartial<Nested>;
// { a?: { b?: { c?: string; }; }; }

15.7.2 键值操作工具类型

// 获取指定类型的键
type KeysOfType<T, U> = {
  [K in keyof T]: T[K] extends U ? K : never;
}[keyof T];
 
interface User {
  id: number;
  name: string;
  email: string;
  age: number;
  isActive: boolean;
}
 
type StringKeys = KeysOfType<User, string>;
// "name" | "email"
 
// 提取指定类型的属性
type PickByType<T, U> = Pick<T, KeysOfType<T, U>>;
type StringProps = PickByType<User, string>;
// { name: string; email: string; }
 
// 排除指定类型的属性
type OmitByType<T, U> = Pick<T, Exclude<keyof T, KeysOfType<T, U>>>;
type NonStringProps = OmitByType<User, string>;
// { id: number; age: number; isActive: boolean; }
 
// 可为 null 的属性
type Nullable<T> = { [P in keyof T]: T[P] | null };
 
// 移除 readonly
type Mutable<T> = { -readonly [P in keyof T]: T[P] };

15.7.3 联合类型工具

// 联合类型转交叉类型
type UnionToIntersection<T> = 
  (T extends any ? (x: T) => void : never) extends 
  (x: infer R) => void ? R : never;
 
type Mixed = { a: 1 } | { b: 2 };
type Intersection = UnionToIntersection<Mixed>;
// { a: 1; b: 2; }
 
// 联合类型转元组(保留顺序)
type UnionToTuple<T, L = LastInUnion<T>> = 
  [T] extends [never] 
    ? [] 
    : [...UnionToTuple<Exclude<T, L>>, L];
 
type LastInUnion<T> = 
  UnionToIntersection<T extends any ? (x: T) => void : never> extends 
  (x: infer L) => void ? L : never;
 
type T = UnionToTuple<"a" | "b" | "c">;
// ["a", "b", "c"] 或其他顺序
 
// 判断类型是否为 never
type IsNever<T> = [T] extends [never] ? true : false;
 
// 判断类型是否为 any
type IsAny<T> = 0 extends (1 & T) ? true : false;

15.8 实战案例

15.8.1 类型安全的 Redux 模式

// Action 创建函数类型
interface Action<T extends string = string, P = any> {
  type: T;
  payload: P;
}
 
type ActionCreator<T extends string, P> = 
  undefined extends P 
    ? () => Action<T, P>
    : (payload: P) => Action<T, P>;
 
// 从 action creators 对象推断 action 类型
type ActionsUnion<A extends Record<string, (...args: any[]) => Action>> = 
  ReturnType<A[keyof A]>;
 
// 使用
const userActions = {
  fetchUser: (id: number) => ({ 
    type: "FETCH_USER" as const, 
    payload: id 
  }),
  updateUser: (user: { id: number; name: string }) => ({ 
    type: "UPDATE_USER" as const, 
    payload: user 
  }),
  deleteUser: (id: number) => ({ 
    type: "DELETE_USER" as const, 
    payload: id 
  })
};
 
type UserAction = ActionsUnion<typeof userActions>;
// { type: "FETCH_USER"; payload: number; } |
// { type: "UPDATE_USER"; payload: { id: number; name: string; }; } |
// { type: "DELETE_USER"; payload: number; }

15.8.2 类型安全的 API 客户端

// 端点定义
interface Endpoints {
  "/users": {
    GET: { response: User[] };
    POST: { body: CreateUserDTO; response: User };
  };
  "/users/:id": {
    GET: { params: { id: string }; response: User };
    PUT: { params: { id: string }; body: UpdateUserDTO; response: User };
    DELETE: { params: { id: string }; response: void };
  };
}
 
// 提取路径参数
type ExtractParams<T extends string> = 
  T extends `${string}:${infer Param}/${infer Rest}`
    ? { [K in Param]: string } & ExtractParams<`/${Rest}`>
    : T extends `${string}:${infer Param}`
    ? { [K in Param]: string }
    : {};
 
// 类型安全请求函数
type RequestMethod = "GET" | "POST" | "PUT" | "DELETE";
 
async function apiRequest<
  Path extends keyof Endpoints,
  Method extends keyof Endpoints[Path]
>(
  path: Path,
  method: Method,
  ...args: Method extends "GET"
    ? Endpoints[Path][Method] extends { params: infer P }
      ? [P]
      : []
    : Endpoints[Path][Method] extends { params: infer P; body: infer B }
    ? [P, B]
    : Endpoints[Path][Method] extends { body: infer B }
    ? [B]
    : []
): Promise<Endpoints[Path][Method] extends { response: infer R } ? R : never> {
  // 实现...
  return null as any;
}
 
// 使用
const users = await apiRequest("/users", "GET");
const user = await apiRequest("/users/:id", "GET", { id: "1" });

15.9 本章小结

本章我们学习了 TypeScript 的内置工具类型:

1. Partial<T> - 所有属性可选

2. Required<T> - 所有属性必需

3. Readonly<T> - 所有属性只读

4. Pick<T, K> - 选取指定属性

5. Omit<T, K> - 排除指定属性

6. Exclude<T, U> - 从联合类型排除

7. Extract<T, U> - 从联合类型提取

8. NonNullable<T> - 排除 null 和 undefined

9. Parameters<T> - 提取函数参数

10. ReturnType<T> - 提取函数返回类型

11. ConstructorParameters<T> - 提取构造函数参数

12. InstanceType<T> - 提取实例类型

13. 字符串工具类型 - Uppercase、Lowercase、Capitalize、Uncapitalize

以及自定义工具类型的创建方法。

15.10 练习题

练习 1:基础工具类型应用

给定以下接口,创建相应的 DTO 类型:

interface Product {
  id: string;
  name: string;
  price: number;
  description: string;
  createdAt: Date;
  updatedAt: Date;
  internalNotes: string;
}
 
// 1. 创建产品请求(排除 id、时间戳和内部字段)
// 2. 更新产品请求(所有字段可选,排除 id)
// 3. 公开产品信息(只包含 name、price、description)
// 4. 产品列表项(公开信息 + id)

参考答案

// 1. 创建产品请求
type CreateProductDTO = Omit<Product, "id" | "createdAt" | "updatedAt" | "internalNotes">;
 
// 2. 更新产品请求
type UpdateProductDTO = Partial<Omit<Product, "id" | "createdAt" | "updatedAt" | "internalNotes">>;
 
// 3. 公开产品信息
type PublicProduct = Pick<Product, "name" | "price" | "description">;
 
// 4. 产品列表项
type ProductListItem = Pick<Product, "id" | "name" | "price" | "description">;
 
// 或者使用组合
type ProductListItem2 = Pick<Product, "id"> & PublicProduct;

练习 2:自定义工具类型

实现以下工具类型:

// 1. 将对象的所有属性路径展平为联合类型
// 例如:{ a: { b: number; c: string } } => "a" | "a.b" | "a.c"
 
type Paths<T> = any;
 
// 2. 根据路径获取对象类型
type PathValue<T, P extends string> = any;
 
// 3. 创建响应式引用类型(类似 Vue 的 Ref)
type Ref<T> = any;

参考答案

// 1. 属性路径
type Paths<T, K extends keyof T = keyof T> = 
  K extends string
    ? T[K] extends Record<string, any>
      ? K | `${K}.${Paths<T[K]>}`
      : K
    : never;
 
interface Nested {
  user: {
    name: string;
    address: {
      city: string;
    };
  };
}
 
type NestedPaths = Paths<Nested>;
// "user" | "user.name" | "user.address" | "user.address.city"
 
// 2. 路径值获取
type PathValue<T, P extends string> = 
  P extends `${infer K}.${infer Rest}`
    ? K extends keyof T
      ? PathValue<T[K], Rest>
      : never
    : P extends keyof T
    ? T[P]
    : never;
 
type City = PathValue<Nested, "user.address.city">;  // string
 
// 3. 响应式引用
type Ref<T> = { value: T };
 
function ref<T>(value: T): Ref<T> {
  return { value };
}
 
const count = ref(0);  // Ref<number>
const user = ref({ name: "Alice" });  // Ref<{ name: string; }>

练习 3:类型安全的表单验证

使用工具类型实现类型安全的表单验证:

// 要求:
// 1. 根据数据模型自动生成验证规则类型
// 2. 验证结果类型包含每个字段的错误信息
// 3. 验证函数返回类型安全的结果

参考答案

// 验证规则
interface ValidationRule<T> {
  required?: boolean;
  min?: number;
  max?: number;
  pattern?: RegExp;
  custom?: (value: T) => string | undefined;
}
 
// 验证器映射
type ValidatorMap<T> = {
  [K in keyof T]?: ValidationRule<T[K]>;
};
 
// 验证错误
type ValidationErrors<T> = {
  [K in keyof T]?: string;
};
 
// 验证结果
type ValidationResult<T> = 
  | { valid: true; errors: null }
  | { valid: false; errors: ValidationErrors<T> };
 
// 验证器类
class FormValidator<T extends Record<string, any>> {
  constructor(private rules: ValidatorMap<T>) {}
 
  validate(data: T): ValidationResult<T> {
    const errors: ValidationErrors<T> = {};
 
    for (const key in this.rules) {
      const rule = this.rules[key];
      const value = data[key];
 
      if (!rule) continue;
 
      // 必填验证
      if (rule.required && (value === undefined || value === null || value === "")) {
        errors[key] = `${String(key)} is required`;
        continue;
      }
 
      // 数值验证
      if (typeof value === "number") {
        if (rule.min !== undefined && value < rule.min) {
          errors[key] = `${String(key)} must be at least ${rule.min}`;
        }
        if (rule.max !== undefined && value > rule.max) {
          errors[key] = `${String(key)} must be at most ${rule.max}`;
        }
      }
 
      // 字符串验证
      if (typeof value === "string") {
        if (rule.pattern && !rule.pattern.test(value)) {
          errors[key] = `${String(key)} format is invalid`;
        }
      }
 
      // 自定义验证
      if (rule.custom) {
        const error = rule.custom(value);
        if (error) {
          errors[key] = error;
        }
      }
    }
 
    const isValid = Object.keys(errors).length === 0;
    return isValid 
      ? { valid: true, errors: null }
      : { valid: false, errors };
  }
}
 
// 使用
interface LoginForm {
  email: string;
  password: string;
  rememberMe: boolean;
}
 
const loginValidator = new FormValidator<LoginForm>({
  email: {
    required: true,
    pattern: /^[^\s@]+@[^\s@]+\.[^\s@]+$/
  },
  password: {
    required: true,
    custom: (value) => value.length < 8 ? "Password must be at least 8 characters" : undefined
  }
});
 
const result = loginValidator.validate({
  email: "invalid",
  password: "short",
  rememberMe: true
});
 
if (!result.valid) {
  console.log(result.errors.email);  // string | undefined
  console.log(result.errors.password);  // string | undefined
}

扩展阅读