映射类型基础
映射类型基础
TypeScript 的映射类型是一种基于已有类型创建新类型的强大工具。它允许我们通过遍历现有类型的属性并应用转换规则来生成新类型,这种机制在处理复杂类型时特别有用。
基本语法
映射类型的核心语法使用 in
关键字遍历联合类型:
type MappedType<T> = {
[P in keyof T]: T[P];
};
这个最简单的映射类型实际上创建了一个与原始类型 T
完全相同的新类型。keyof T
获取类型 T
的所有属性名组成的联合类型,P in
则遍历这个联合类型中的每个属性。
常见内置映射类型
TypeScript 内置了几个常用的映射类型:
// 使所有属性变为可选
type Partial<T> = {
[P in keyof T]?: T[P];
};
// 使所有属性变为必需
type Required<T> = {
[P in keyof T]-?: T[P];
};
// 使所有属性变为只读
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};
属性修饰符操作
映射类型可以操作属性的修饰符(?
和 readonly
):
// 移除可选修饰符
type Concrete<T> = {
[P in keyof T]-?: T[P];
};
// 添加readonly修饰符
type Locked<T> = {
+readonly [P in keyof T]: T[P];
};
// 移除readonly修饰符
type Mutable<T> = {
-readonly [P in keyof T]: T[P];
};
键名重映射
TypeScript 4.1 引入了键名重映射,允许在映射过程中修改属性名:
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;
// }
条件类型与映射类型结合
映射类型可以与条件类型结合使用,实现更复杂的类型转换:
type NullableProperties<T> = {
[P in keyof T]: T[P] | null;
};
interface User {
id: number;
name: string;
email: string;
}
type NullableUser = NullableProperties<User>;
// 等价于:
// {
// id: number | null;
// name: string | null;
// email: string | null;
// }
过滤属性
通过条件类型可以过滤掉某些属性:
type OnlyFunctions<T> = {
[P in keyof T as T[P] extends Function ? P : never]: T[P];
};
interface Mixed {
id: number;
name: string;
update: (data: Partial<Mixed>) => void;
delete: () => Promise<void>;
}
type Functions = OnlyFunctions<Mixed>;
// 等价于:
// {
// update: (data: Partial<Mixed>) => void;
// delete: () => Promise<void>;
// }
实际应用示例
表单类型生成
interface User {
username: string;
password: string;
age: number;
}
type FormField<T> = {
value: T;
isValid: boolean;
errorMessage?: string;
};
type UserForm = {
[P in keyof User]: FormField<User[P]>;
};
// 等价于:
// {
// username: FormField<string>;
// password: FormField<string>;
// age: FormField<number>;
// }
API响应包装
type ApiResponse<T> = {
data: T;
status: number;
timestamp: Date;
};
type Entity = {
id: string;
createdAt: Date;
};
type EntityResponse<T extends Entity> = ApiResponse<{
[P in keyof T]: T[P];
}>;
interface Product extends Entity {
name: string;
price: number;
}
type ProductResponse = EntityResponse<Product>;
// 等价于:
// ApiResponse<{
// id: string;
// createdAt: Date;
// name: string;
// price: number;
// }>
高级模式
递归映射
映射类型可以递归应用于嵌套对象:
type DeepReadonly<T> = {
readonly [P in keyof T]: T[P] extends object ? DeepReadonly<T[P]> : T[P];
};
interface Company {
name: string;
departments: {
name: string;
employees: number;
}[];
}
type ReadonlyCompany = DeepReadonly<Company>;
// 等价于:
// {
// readonly name: string;
// readonly departments: readonly {
// readonly name: string;
// readonly employees: number;
// }[];
// }
联合类型映射
映射类型也可以用于联合类型:
type EventMap = {
click: MouseEvent;
focus: FocusEvent;
keydown: KeyboardEvent;
};
type Handlers<T> = {
[P in keyof T as `on${Capitalize<string & P>}`]: (event: T[P]) => void;
};
type EventHandlers = Handlers<EventMap>;
// 等价于:
// {
// onClick: (event: MouseEvent) => void;
// onFocus: (event: FocusEvent) => void;
// onKeydown: (event: KeyboardEvent) => void;
// }
性能考虑
虽然映射类型非常强大,但在处理大型类型或深度嵌套类型时需要注意性能问题。TypeScript 编译器需要展开这些类型,复杂的映射类型可能会导致编译速度变慢或内存使用增加。
// 避免过度嵌套的映射类型
type OverlyComplex<T> = {
[P in keyof T]: {
[Q in keyof T[P]]: {
[R in keyof T[P][Q]]: T[P][Q][R];
};
};
};
与模板字面量类型结合
TypeScript 4.1 引入的模板字面量类型可以与映射类型完美配合:
type WithChanges<T> = {
[P in keyof T as `${string & P}Changed`]: (newValue: T[P]) => void;
};
interface Settings {
theme: string;
fontSize: number;
}
type SettingsChanges = WithChanges<Settings>;
// 等价于:
// {
// themeChanged: (newValue: string) => void;
// fontSizeChanged: (newValue: number) => void;
// }
实用工具类型实现
许多实用工具类型都是基于映射类型实现的:
// 选择特定属性
type Pick<T, K extends keyof T> = {
[P in K]: T[P];
};
// 排除特定属性
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;
// 记录类型
type Record<K extends keyof any, T> = {
[P in K]: T;
};
类型谓词与映射类型
映射类型可以与类型谓词结合,创建更精确的类型守卫:
type TypeGuardMap<T> = {
[P in keyof T]: (value: unknown) => value is T[P];
};
interface Config {
port: number;
hostname: string;
ssl: boolean;
}
const configGuards: TypeGuardMap<Config> = {
port: (value): value is number => typeof value === 'number',
hostname: (value): value is string => typeof value === 'string',
ssl: (value): value is boolean => typeof value === 'boolean',
};
映射类型与泛型约束
映射类型可以配合泛型约束创建更安全的API:
type NonMethodKeys<T> = {
[P in keyof T]: T[P] extends Function ? never : P;
}[keyof T];
type DataProperties<T> = Pick<T, NonMethodKeys<T>>;
class User {
id: string;
name: string;
save() {
// 保存逻辑
}
}
type UserData = DataProperties<User>;
// 等价于:
// {
// id: string;
// name: string;
// }
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn
上一篇:标记线与标记点