自定义工具类型
概述
虽然 TypeScript 提供了丰富的内置工具类型,但在实际开发中,我们经常需要创建符合特定业务需求的自定义工具类型。自定义工具类型基于映射类型和条件类型实现,可以帮助我们解决特定的类型问题,提高代码的类型安全性和可维护性。掌握自定义工具类型的创建方法,是成为 TypeScript 高级开发者的重要技能。
为什么需要自定义工具类型
内置工具类型虽然强大,但有时无法满足特定需求:
// 场景 1:需要深度只读类型(内置 Readonly 只影响第一层)
interface User {
name: string;
address: {
city: string;
street: string;
};
}
type ReadonlyUser = Readonly<User>;
// address 属性仍然可以修改
// 需要自定义深度只读类型
type DeepReadonly<T> = {
readonly [P in keyof T]: T[P] extends object ? DeepReadonly<T[P]> : T[P];
};
// 场景 2:需要提取函数类型的参数名
// 内置工具类型无法直接获取参数名
// 场景 3:需要根据条件选择不同的类型转换
// 需要自定义条件逻辑自定义工具类型让我们能够:
- 解决特定业务问题:针对项目中的特定需求创建类型工具
- 提高代码复用性:将常用的类型转换逻辑封装为工具类型
- 增强类型安全:创建更精确的类型约束
- 简化复杂类型:将复杂的类型定义分解为可复用的工具类型
基础自定义工具类型
深度只读类型(DeepReadonly)
内置的 Readonly 只影响第一层属性,对于嵌套对象需要使用递归类型:
// 深度只读类型实现
type DeepReadonly<T> = {
readonly [P in keyof T]: T[P] extends object
? T[P] extends Function
? T[P] // 函数类型不需要递归
: DeepReadonly<T[P]>
: T[P];
};
// 使用示例
interface User {
name: string;
age: number;
address: {
city: string;
street: string;
coordinates: {
lat: number;
lng: number;
};
};
getInfo(): string;
}
type ReadonlyUser = DeepReadonly<User>;
// 类型:{
// readonly name: string;
// readonly age: number;
// readonly address: {
// readonly city: string;
// readonly street: string;
// readonly coordinates: {
// readonly lat: number;
// readonly lng: number;
// };
// };
// readonly getInfo: () => string;
// }
const user: ReadonlyUser = {
name: "John",
age: 30,
address: {
city: "Beijing",
street: "Main St",
coordinates: { lat: 39.9, lng: 116.4 }
},
getInfo() { return `${this.name}: ${this.age}`; }
};
// ❌ 错误:所有层级都是只读的
// user.name = "Jane";
// user.address.city = "Shanghai";
// user.address.coordinates.lat = 40.0;深度可选类型(DeepPartial)
类似地,我们可以创建深度可选类型:
// 深度可选类型实现
type DeepPartial<T> = {
[P in keyof T]?: T[P] extends object
? T[P] extends Function
? T[P]
: DeepPartial<T[P]>
: T[P];
};
// 使用示例
interface Config {
api: {
baseUrl: string;
timeout: number;
retry: {
maxAttempts: number;
delay: number;
};
};
cache: {
enabled: boolean;
ttl: number;
};
}
type PartialConfig = DeepPartial<Config>;
// 所有嵌套属性都变为可选
const config: PartialConfig = {
api: {
baseUrl: "https://api.example.com"
// timeout 和 retry 都是可选的
}
// cache 也是可选的
};深度必需类型(DeepRequired)
与 DeepPartial 相反,创建深度必需类型:
// 深度必需类型实现
type DeepRequired<T> = {
[P in keyof T]-?: T[P] extends object
? T[P] extends Function
? T[P]
: DeepRequired<T[P]>
: T[P];
};
// 使用示例
interface OptionalConfig {
api?: {
baseUrl?: string;
timeout?: number;
};
cache?: {
enabled?: boolean;
};
}
type RequiredConfig = DeepRequired<OptionalConfig>;
// 所有嵌套属性都变为必需
const config: RequiredConfig = {
api: {
baseUrl: "https://api.example.com", // 必需
timeout: 5000 // 必需
},
cache: {
enabled: true // 必需
}
};属性操作工具类型
提取可选属性键(OptionalKeys)
提取对象类型中所有可选属性的键:
// 提取可选属性键
type OptionalKeys<T> = {
[K in keyof T]-?: {} extends Pick<T, K> ? K : never;
}[keyof T];
// 使用示例
interface User {
name: string; // 必需
age?: number; // 可选
email: string; // 必需
phone?: string; // 可选
}
type Optional = OptionalKeys<User>;
// 类型:"age" | "phone"提取必需属性键(RequiredKeys)
提取对象类型中所有必需属性的键:
// 提取必需属性键
type RequiredKeys<T> = {
[K in keyof T]-?: {} extends Pick<T, K> ? never : K;
}[keyof T];
// 使用示例
interface User {
name: string;
age?: number;
email: string;
phone?: string;
}
type Required = RequiredKeys<User>;
// 类型:"name" | "email"排除函数属性(OmitFunctions)
从对象类型中排除所有函数类型的属性:
// 排除函数属性
type OmitFunctions<T> = {
[K in keyof T as T[K] extends Function ? never : K]: T[K];
};
// 使用示例
interface User {
name: string;
age: number;
getName(): string;
getAge(): number;
updateName(name: string): void;
}
type UserData = OmitFunctions<User>;
// 类型:{ name: string; age: number; }提取函数属性(PickFunctions)
只保留对象类型中的函数属性:
// 提取函数属性
type PickFunctions<T> = {
[K in keyof T as T[K] extends Function ? K : never]: T[K];
};
// 使用示例
interface User {
name: string;
age: number;
getName(): string;
getAge(): number;
updateName(name: string): void;
}
type UserMethods = PickFunctions<User>;
// 类型:{
// getName: () => string;
// getAge: () => number;
// updateName: (name: string) => void;
// }函数类型工具
提取函数参数名(FunctionParameterNames)
虽然无法直接获取参数名,但可以提取参数类型:
// 提取函数的所有参数类型
type FunctionParameters<T extends (...args: any) => any> =
T extends (...args: infer P) => any ? P : never;
// 提取第一个参数类型
type FirstParameter<T extends (...args: any) => any> =
T extends (first: infer P, ...args: any[]) => any ? P : never;
// 提取最后一个参数类型
type LastParameter<T extends (...args: any) => any> =
T extends (...args: any[]) => infer P ? P : never;
// 使用示例
function createUser(name: string, age: number, email: string): void {}
type Params = FunctionParameters<typeof createUser>;
// 类型:[string, number, string]
type First = FirstParameter<typeof createUser>;
// 类型:string
type Last = LastParameter<typeof createUser>;
// 类型:string函数重载类型提取
处理函数重载的情况:
// 提取所有重载签名的返回类型
type OverloadReturnType<T> =
T extends {
(...args: any[]): infer R1;
(...args: any[]): infer R2;
(...args: any[]): infer R3;
(...args: any[]): infer R4;
(...args: any[]): infer R5;
} ? R1 | R2 | R3 | R4 | R5 :
T extends {
(...args: any[]): infer R1;
(...args: any[]): infer R2;
(...args: any[]): infer R3;
(...args: any[]): infer R4;
} ? R1 | R2 | R3 | R4 :
T extends {
(...args: any[]): infer R1;
(...args: any[]): infer R2;
(...args: any[]): infer R3;
} ? R1 | R2 | R3 :
T extends {
(...args: any[]): infer R1;
(...args: any[]): infer R2;
} ? R1 | R2 :
T extends (...args: any[]) => infer R ? R : any;类型判断工具
判断是否为数组类型(IsArray)
// 判断是否为数组类型
type IsArray<T> = T extends any[] ? true : false;
// 使用示例
type Test1 = IsArray<string[]>; // 类型:true
type Test2 = IsArray<number>; // 类型:false
type Test3 = IsArray<readonly string[]>; // 类型:true判断是否为函数类型(IsFunction)
// 判断是否为函数类型
type IsFunction<T> = T extends (...args: any[]) => any ? true : false;
// 使用示例
type Test1 = IsFunction<() => void>; // 类型:true
type Test2 = IsFunction<(x: number) => string>; // 类型:true
type Test3 = IsFunction<string>; // 类型:false判断是否为 Promise 类型(IsPromise)
// 判断是否为 Promise 类型
type IsPromise<T> = T extends Promise<infer P> ? true : false;
// 使用示例
type Test1 = IsPromise<Promise<string>>; // 类型:true
type Test2 = IsPromise<string>; // 类型:false类型转换工具
将联合类型转换为交叉类型(UnionToIntersection)
// 联合类型转交叉类型
type UnionToIntersection<U> =
(U extends any ? (x: U) => void : never) extends (x: infer I) => void
? I
: never;
// 使用示例
type Union = { a: string } | { b: number } | { c: boolean };
type Intersection = UnionToIntersection<Union>;
// 类型:{ a: string } & { b: number } & { c: boolean }将元组类型转换为联合类型(TupleToUnion)
// 元组类型转联合类型
type TupleToUnion<T extends readonly any[]> = T[number];
// 使用示例
type Tuple = [string, number, boolean];
type Union = TupleToUnion<Tuple>;
// 类型:string | number | boolean将对象类型转换为联合类型(ObjectToUnion)
// 对象类型转联合类型(值)
type ObjectToUnion<T> = T[keyof T];
// 使用示例
type Status = {
pending: "pending";
approved: "approved";
rejected: "rejected";
};
type StatusValues = ObjectToUnion<Status>;
// 类型:"pending" | "approved" | "rejected"实际应用场景
场景 1:API 响应类型处理
// 定义 API 响应类型
interface ApiResponse<T> {
data: T;
status: number;
message: string;
timestamp: number;
}
// 提取数据部分
type ExtractData<T> = T extends ApiResponse<infer D> ? D : never;
// 使用示例
type UserResponse = ApiResponse<{ id: number; name: string }>;
type UserData = ExtractData<UserResponse>;
// 类型:{ id: number; name: string }
// 创建只读的 API 响应类型
type ReadonlyApiResponse<T> = DeepReadonly<ApiResponse<T>>;场景 2:表单验证类型
// 表单字段类型
interface FormFields {
name: string;
email: string;
age: number;
phone?: string;
}
// 验证错误类型:每个字段可能有错误信息
type ValidationErrors<T> = Partial<Record<keyof T, string>>;
// 表单状态类型
type FormState<T> = {
values: T;
errors: ValidationErrors<T>;
touched: Partial<Record<keyof T, boolean>>;
isValid: boolean;
};
// 使用示例
type UserFormState = FormState<FormFields>;场景 3:状态管理类型
// 定义状态类型
interface State {
user: {
name: string;
age: number;
};
settings: {
theme: "light" | "dark";
language: string;
};
}
// 创建只读状态类型
type ReadonlyState = DeepReadonly<State>;
// 创建状态更新函数类型
type StateUpdater<T> = (state: T) => Partial<T>;
// 创建选择器函数类型
type Selector<T, R> = (state: T) => R;
// 使用示例
const selectUserName: Selector<State, string> = (state) => state.user.name;场景 4:事件处理类型
// 定义事件类型
type EventMap = {
click: MouseEvent;
change: Event;
submit: SubmitEvent;
keydown: KeyboardEvent;
};
// 创建事件处理器类型
type EventHandlers<T> = {
[K in keyof T]: (event: T[K]) => void;
};
// 使用示例
const handlers: EventHandlers<EventMap> = {
click: (e) => console.log("clicked", e.clientX),
change: (e) => console.log("changed", e.target),
submit: (e) => console.log("submitted", e.submitter),
keydown: (e) => console.log("key pressed", e.key)
};高级工具类型组合
条件深度操作
根据条件决定是否进行深度操作:
// 条件深度只读
type ConditionalDeepReadonly<T, Condition extends boolean> =
Condition extends true ? DeepReadonly<T> : Readonly<T>;
// 使用示例
type User = {
name: string;
address: { city: string };
};
type ShallowReadonly = ConditionalDeepReadonly<User, false>;
type DeepReadonly = ConditionalDeepReadonly<User, true>;选择性属性操作
只对特定属性进行转换:
// 只对指定键进行深度只读
type SelectiveDeepReadonly<T, K extends keyof T> =
Omit<T, K> & {
readonly [P in K]: T[P] extends object
? DeepReadonly<T[P]>
: T[P];
};
// 使用示例
interface Config {
api: { baseUrl: string };
cache: { ttl: number };
name: string;
}
type PartialReadonly = SelectiveDeepReadonly<Config, "api">;
// api 属性是深度只读,其他属性保持原样注意事项
注意
递归深度限制:TypeScript 对递归类型有深度限制(默认约 50 层),过深的递归可能导致类型检查失败。
性能影响:复杂的自定义工具类型可能影响类型检查性能,特别是在大型项目中。
类型推断:某些复杂的工具类型可能影响 TypeScript 的类型推断能力,导致需要显式类型注解。
可读性:过于复杂的工具类型可能降低代码可读性,建议添加详细的注释说明。
测试:自定义工具类型应该通过实际使用场景进行测试,确保行为符合预期。
最佳实践
从简单开始:先创建简单的工具类型,逐步增加复杂度。
复用内置类型:尽量基于内置工具类型构建,而不是从头实现。
命名清晰:使用清晰、描述性的名称,遵循命名约定(如
Deep、Optional等前缀)。添加注释:为复杂的工具类型添加注释,说明其用途和实现原理。
类型测试:使用类型测试验证工具类型的正确性。
文档化:在项目文档中记录自定义工具类型的用途和使用方法。
类型测试技巧
验证自定义工具类型是否正确工作:
// 类型相等性检查
type Expect<T extends true> = T;
type Equal<X, Y> =
(<T>() => T extends X ? 1 : 2) extends
(<T>() => T extends Y ? 1 : 2) ? true : false;
// 使用示例
type Test1 = Expect<Equal<DeepReadonly<{ a: { b: string } }>,
{ readonly a: { readonly b: string } }>>;
// 如果类型不匹配,会报错
// 类型包含检查
type ExpectExtends<T, U> = T extends U ? true : false;
type Test2 = ExpectExtends<{ readonly a: string }, DeepReadonly<{ a: string }>>;
// 检查类型是否满足约束