🚀 TypeScript中的泛型如何与映射类型的结合使用

📅 发布于 2026年1月 | 👤 作者:博主 | 🏷️ 标签:TypeScript, 泛型, 映射类型, 类型转换, Web开发, 前端, 面试

欢迎来到我的博客文章!所有文章都是满满的前端干货,文章简明扼要。

什么是映射类型?

映射类型基于现有类型创建新类型,通过遍历原类型的属性并应用转换规则。

// 基本语法
type MappedType<T> = {
  [P in keyof T]: T[P];
};

泛型与映射类型的基础结合

1. 基础映射类型

将所有属性变为可选

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

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

type PartialUser = MyPartial<User>;
// 等价于: { id?: number; name?: string; email?: string }

将所有属性变为只读

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

type ReadonlyUser = MyReadonly<User>;
// 等价于: { readonly id: number; readonly name: string; readonly email: string }

2. 属性修饰符操作

移除 readonly 修饰符

// 移除 readonly 修饰符
type MyMutable<T> = {
  -readonly [P in keyof T]: T[P];
};

type MutableUser = MyMutable<ReadonlyUser>;
// 等价于: { id: number; name: string; email: string }

移除可选修饰符

// 移除可选修饰符
type MyRequired<T> = {
  [P in keyof T]-?: T[P];
};

interface Config {
  host?: string;
  port?: number;
  timeout?: number;
}

type RequiredConfig = MyRequired<Config>;
// 等价于: { host: string; port: number; timeout: number }

3. Pick 和 Omit 映射类型

Pick - 选择特定属性

// Pick - 选择特定属性
type MyPick<T, K extends keyof T> = {
  [P in K]: T[P];
};

type UserBasic = MyPick<User, 'id' | 'name'>;
// 等价于: { id: number; name: string }

Omit - 排除特定属性

// Omit - 排除特定属性
type MyOmit<T, K extends keyof any> = {
  [P in keyof T as P extends K ? never : P]: T[P];
};

type UserWithoutEmail = MyOmit<User, 'email'>;
// 等价于: { id: number; name: string }

高级映射类型应用

4. 条件映射类型

// 将字符串类型属性变为可选,其他保持不变
type PartialString<T> = {
  [P in keyof T]: T[P] extends string ? T[P] | undefined : T[P];
};

interface Product {
  id: number;
  name: string;
  price: number;
  description: string;
}

type PartialStringProduct = PartialString<Product>;
// 等价于: {
// id: number;
// name: string | undefined;
// price: number;
// description: string | undefined;
// }

5. 键名重映射(Key Remapping)

// 为所有属性添加 get 前缀
type Getters<T> = {
  [P in keyof T as `get$${Capitalize<string & P>$}`]: () => T[P];
};

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

type PersonGetters = Getters<Person>;
// 等价于: {
// getName: () => string;
// getAge: () => number;
// }

6. 过滤属性类型

// 只保留特定类型的属性
type PickByType<T, U> = {
  [P in keyof T as T[P] extends U ? P : never]: T[P];
};

interface Mixed {
  id: number;
  name: string;
  age: number;
  email: string;
}

type NumberProps = PickByType<Mixed, number>;
// 等价于: { id: number; age: number }

type StringProps = PickByType<Mixed, string>;
// 等价于: { name: string; email: string }

实战应用场景

7. API 响应类型转换

// 将 API 响应的所有字段变为可选(用于部分更新)
interface UserAPI {
  id: number;
  username: string;
  email: string;
  createdAt: Date;
}

type UserUpdatePayload = Partial<Omit<UserAPI, 'id' | 'createdAt'>>;
// 等价于: { username?: string; email?: string }

// 使用示例
async function updateUser(id: number, data: UserUpdatePayload) {
  return fetch('/api/users/' + id, {
    method: 'PATCH',
    body: JSON.stringify(data),
  });
}

8. 表单状态管理

// 为表单字段生成错误状态类型
type FormErrors<T> = {
  [P in keyof T]?: string;
};

interface LoginForm {
  username: string;
  password: string;
}

type LoginFormErrors = FormErrors<LoginForm>;
// 等价于: { username?: string; password?: string }

// 使用示例
const errors: LoginFormErrors = {
  username: '用户名不能为空',
  password: '密码至少8位',
};

9. 深度只读/可选

// 递归实现深度只读
type DeepReadonly<T> = {
  readonly [P in keyof T]: T[P] extends object
    ? DeepReadonly<T[P]>
    : T[P];
};

interface NestedConfig {
  database: {
    host: string;
    port: number;
    credentials: {
      username: string;
      password: string;
    };
  };
}

type ReadonlyConfig = DeepReadonly<NestedConfig>;
// 所有嵌套属性都变为只读

常用映射类型对比

映射类型 语法 作用 示例
Partial [P in keyof T]? 所有属性变为可选 Partial<User>
Required [P in keyof T]-? 移除所有可选修饰符 Required<Config>
Readonly readonly [P in keyof T] 所有属性变为只读 Readonly<User>
Mutable -readonly [P in keyof T] 移除所有只读修饰符 Mutable<ReadonlyUser>
Pick [P in K] 选择特定属性 Pick<User, 'id' | 'name'>
Omit [P in keyof T as ...] 排除特定属性 Omit<User, 'email'>

核心要点总结

💡 关键知识点

✅ 实战建议

← 返回首页