泛型与映射类型结合
泛型与映射类型结合的基本概念
TypeScript中泛型允许创建可重用的组件,而映射类型则能基于现有类型生成新类型。两者结合使用时,可以构建出更灵活、更强大的类型系统。泛型提供了类型参数化的能力,映射类型则通过遍历已有类型的属性来转换类型,这种组合特别适合处理需要动态生成类型的场景。
type Partial<T> = {
[P in keyof T]?: T[P];
};
interface User {
id: number;
name: string;
age: number;
}
type PartialUser = Partial<User>;
// 等价于
// interface PartialUser {
// id?: number;
// name?: string;
// age?: number;
// }
映射类型的核心语法
映射类型的核心语法是[P in K]: T
形式,其中K
通常是keyof T
,表示遍历类型T
的所有属性名。结合泛型后,可以创建出能应用于任何类型的通用映射类型。
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};
type Nullable<T> = {
[P in keyof T]: T[P] | null;
};
interface Product {
id: string;
price: number;
inStock: boolean;
}
type ReadonlyProduct = Readonly<Product>;
// {
// readonly id: string;
// readonly price: number;
// readonly inStock: boolean;
// }
type NullableProduct = Nullable<Product>;
// {
// id: string | null;
// price: number | null;
// inStock: boolean | null;
// }
条件类型与映射类型的结合
当映射类型与条件类型结合时,可以实现更复杂的类型转换。通过extends
关键字,可以在映射过程中对属性类型进行条件判断和转换。
type FilterProperties<T, U> = {
[P in keyof T as T[P] extends U ? P : never]: T[P];
};
interface MixedData {
name: string;
age: number;
isAdmin: boolean;
createdAt: Date;
}
type StringProperties = FilterProperties<MixedData, string>;
// { name: string }
type NumberProperties = FilterProperties<MixedData, number>;
// { age: number }
递归映射类型
映射类型可以递归应用,用于处理嵌套对象结构。这在处理复杂的数据形状时特别有用,比如深度只读或深度可选类型。
type DeepReadonly<T> = {
readonly [P in keyof T]: T[P] extends object ? DeepReadonly<T[P]> : T[P];
};
interface Company {
name: string;
departments: {
sales: {
headcount: number;
budget: number;
};
engineering: {
headcount: number;
projects: string[];
};
};
}
type ReadonlyCompany = DeepReadonly<Company>;
// {
// readonly name: string;
// readonly departments: {
// readonly sales: {
// readonly headcount: number;
// readonly budget: number;
// };
// readonly engineering: {
// readonly headcount: number;
// readonly projects: readonly string[];
// };
// };
// }
键重映射的高级用法
TypeScript 4.1引入了键重映射功能,允许在映射过程中修改属性名。这通过as
子句实现,为类型转换提供了更多可能性。
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 RemoveMethods<T> = {
[P in keyof T as T[P] extends Function ? never : P]: T[P];
};
class Example {
id: number = 1;
calculate(): number {
return this.id * 2;
}
}
type DataOnly = RemoveMethods<Example>;
// { id: number }
实用工具类型的实现
许多TypeScript内置工具类型实际上就是泛型与映射类型的结合。理解这些实现有助于创建自定义工具类型。
type Pick<T, K extends keyof T> = {
[P in K]: T[P];
};
type Record<K extends keyof any, T> = {
[P in K]: T;
};
interface Book {
title: string;
author: string;
pages: number;
published: boolean;
}
type BookPreview = Pick<Book, 'title' | 'author'>;
// { title: string; author: string }
type PageCounts = Record<'home' | 'about' | 'contact', number>;
// {
// home: number;
// about: number;
// contact: number;
// }
处理联合类型的映射
当映射类型应用于联合类型时,会产生分布式条件类型的效果。这在处理可辨识联合类型时特别有用。
type EventMap = {
click: { x: number; y: number };
focus: { target: HTMLElement };
keydown: { key: string; code: number };
};
type EventHandlers<T> = {
[K in keyof T as `on${Capitalize<string & K>}`]: (event: T[K]) => void;
};
type AppEventHandlers = EventHandlers<EventMap>;
// {
// onClick: (event: { x: number; y: number }) => void;
// onFocus: (event: { target: HTMLElement }) => void;
// onKeydown: (event: { key: string; code: number }) => void;
// }
类型谓词与映射类型
结合类型谓词,可以在映射类型中实现更精确的类型守卫,这在处理复杂类型系统时能提供更好的类型安全性。
type NonNullablePropertyKeys<T> = {
[K in keyof T]: null extends T[K] ? never : K;
}[keyof T];
type NonNullableProperties<T> = Pick<T, NonNullablePropertyKeys<T>>;
interface Settings {
theme: 'light' | 'dark';
fontSize?: number;
animations: boolean | null;
}
type RequiredSettings = NonNullableProperties<Settings>;
// { theme: 'light' | 'dark' }
模板字面量类型的映射应用
结合模板字面量类型,映射类型可以创建出更具表达力的API接口,特别是在处理事件处理程序或数据转换时。
type ActionTypes = 'create' | 'update' | 'delete';
type ActionHandlers<T> = {
[K in T as `handle${Capitalize<string & K>}`]: (payload: any) => void;
};
type AppActions = ActionHandlers<ActionTypes>;
// {
// handleCreate: (payload: any) => void;
// handleUpdate: (payload: any) => void;
// handleDelete: (payload: any) => void;
// }
type ApiEndpoints = 'users' | 'products' | 'orders';
type ApiUrls = {
[K in ApiEndpoints as `get${Capitalize<K>}`]: string;
} & {
[K in ApiEndpoints as `post${Capitalize<K>}`]: string;
};
const urls: ApiUrls = {
getUsers: '/api/users',
postUsers: '/api/users',
getProducts: '/api/products',
postProducts: '/api/products',
getOrders: '/api/orders',
postOrders: '/api/orders'
};
性能考虑与优化
虽然映射类型强大,但在处理大型类型或深度嵌套类型时可能会影响编译器性能。了解一些优化技巧很重要。
// 避免过度嵌套
type SimpleMapped<T> = {
[P in keyof T]: SomeUtilityType<T[P]>;
};
// 使用条件类型提前终止递归
type SafeDeepReadonly<T> = T extends object
? {
readonly [P in keyof T]: SafeDeepReadonly<T[P]>;
}
: T;
// 对于大型接口,考虑分解映射
type FirstHalf<T> = Pick<T, /* 选择部分键 */>;
type SecondHalf<T> = Pick<T, /* 选择剩余键 */>;
type MappedFirstHalf<T> = {
[P in keyof FirstHalf<T>]: /* 转换 */;
};
type MappedSecondHalf<T> = {
[P in keyof SecondHalf<T>]: /* 转换 */;
};
type FinalType<T> = MappedFirstHalf<T> & MappedSecondHalf<T>;
实际应用场景示例
在实际项目中,泛型与映射类型的结合可以解决许多常见问题,比如API响应处理、表单状态管理等。
// API响应标准化
type ApiResponse<T> =
| {
status: 'success';
data: T;
timestamp: Date;
}
| {
status: 'error';
error: string;
code: number;
timestamp: Date;
};
type MakeAllPropertiesOptional<T> = {
[P in keyof T]?: T[P];
};
type FormState<T> = {
values: T;
errors: MakeAllPropertiesOptional<Record<keyof T, string>>;
touched: MakeAllPropertiesOptional<Record<keyof T, boolean>>;
isSubmitting: boolean;
};
interface LoginForm {
email: string;
password: string;
rememberMe: boolean;
}
const initialFormState: FormState<LoginForm> = {
values: {
email: '',
password: '',
rememberMe: false
},
errors: {},
touched: {},
isSubmitting: false
};
// 动态生成Redux action类型
type ActionMap<M extends { [index: string]: any }> = {
[Key in keyof M]: M[Key] extends undefined
? {
type: Key;
}
: {
type: Key;
payload: M[Key];
};
};
type CounterActions = ActionMap<{
increment: undefined;
decrement: undefined;
setCount: number;
reset: string;
}>;
// 生成的类型:
// {
// increment: { type: 'increment' };
// decrement: { type: 'decrement' };
// setCount: { type: 'setCount'; payload: number };
// reset: { type: 'reset'; payload: string };
// }
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn