🚀 TypeScript中的泛型如何与映射类型的结合使用
欢迎来到我的博客文章!所有文章都是满满的前端干货,文章简明扼要。
什么是映射类型?
映射类型基于现有类型创建新类型,通过遍历原类型的属性并应用转换规则。
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>;
将所有属性变为只读
type MyReadonly<T> = {
readonly [P in keyof T]: T[P];
};
type ReadonlyUser = MyReadonly<User>;
2. 属性修饰符操作
移除 readonly 修饰符
type MyMutable<T> = {
-readonly [P in keyof T]: T[P];
};
type MutableUser = MyMutable<ReadonlyUser>;
移除可选修饰符
type MyRequired<T> = {
[P in keyof T]-?: T[P];
};
interface Config {
host?: string;
port?: number;
timeout?: number;
}
type RequiredConfig = MyRequired<Config>;
3. Pick 和 Omit 映射类型
Pick - 选择特定属性
type MyPick<T, K extends keyof T> = {
[P in K]: T[P];
};
type UserBasic = MyPick<User, 'id' | 'name'>;
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'>;
高级映射类型应用
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>;
5. 键名重映射(Key Remapping)
type Getters<T> = {
[P in keyof T as `get$${Capitalize<string & P>$}`]: () => T[P];
};
interface Person {
name: string;
age: number;
} }
type PersonGetters = Getters<Person>;
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>;
type StringProps = PickByType<Mixed, string>;
实战应用场景
7. API 响应类型转换
interface UserAPI {
id: number;
username: string;
email: string;
createdAt: Date;
}
type UserUpdatePayload = Partial<Omit<UserAPI, 'id' | 'createdAt'>>;
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>;
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'> |
核心要点总结
💡 关键知识点
- 映射类型语法:[P in keyof T] 遍历类型的所有属性
- 修饰符操作:使用 ? 添加可选,-? 移除可选;readonly 添加只读,-readonly 移除只读
- 键名重映射:使用 as 子句重新映射键名
- 条件类型:结合 extends 实现条件判断
- 泛型约束:K extends keyof T 确保类型安全
✅ 实战建议
- 使用 Partial 处理可选更新场景
- 使用 Pick/Omit 创建精简的类型
- 使用 Readonly 保护不可变数据
- 使用键名重映射生成 getter/setter
- 结合条件类型实现复杂的类型转换