显示页面讨论过去修订反向链接回到顶部 本页面只读。您可以查看源文件,但不能更改它。如果您觉得这是系统错误,请联系管理员。 ====== 第十五章:工具类型 ====== ===== 本章概述 ===== TypeScript 提供了一系列内置的工具类型(Utility Types),用于常见的类型转换操作。这些工具类型基于映射类型和条件类型实现,能够帮助我们高效地创建新的类型。本章将详细介绍所有内置工具类型的使用方法,并展示如何创建自定义工具类型。 ===== 15.1 基础工具类型 ===== ==== 15.1.1 Partial<T> - 可选属性 ==== 将所有属性变为可选的。 <code typescript> type Partial<T> = { [P in keyof T]?: T[P]; }; // 使用示例 interface User { id: number; name: string; email: string; age: number; } // 创建用户时,id 由数据库生成 type CreateUserDTO = Partial<User>; // { // id?: number; // name?: string; // email?: string; // age?: number; // } // 更精确:只让部分属性可选 type CreateUserInput = Omit<User, "id"> & Partial<Pick<User, "age">>; // { // name: string; // email: string; // age?: number; // } // 实际应用:更新函数 function updateUser(user: User, updates: Partial<User>): User { return { ...user, ...updates }; } const user: User = { id: 1, name: "Alice", email: "alice@example.com", age: 25 }; const updated = updateUser(user, { age: 26 }); // OK,只更新 age </code> ==== 15.1.2 Required<T> - 必需属性 ==== 将所有属性变为必需的(移除可选修饰符)。 <code typescript> type Required<T> = { [P in keyof T]-?: T[P]; }; // 使用示例 interface Config { host: string; port?: number; ssl?: boolean; timeout?: number; } type StrictConfig = Required<Config>; // { // host: string; // port: number; // ssl: boolean; // timeout: number; // } // 实际应用:配置合并后的校验 function validateConfig(config: Required<Config>): boolean { return config.port > 0 && config.port < 65536 && config.timeout > 0; } const fullConfig: Required<Config> = { host: "localhost", port: 3000, ssl: false, timeout: 5000 }; </code> ==== 15.1.3 Readonly<T> - 只读属性 ==== 将所有属性变为只读的。 <code typescript> type Readonly<T> = { readonly [P in keyof T]: T[P]; }; // 使用示例 interface Point { x: number; y: number; } type ImmutablePoint = Readonly<Point>; // { // readonly x: number; // readonly y: number; // } const point: ImmutablePoint = { x: 0, y: 0 }; // point.x = 1; // Error: Cannot assign to 'x' because it is a read-only property // 实际应用:常量配置 const API_CONFIG: Readonly<{ baseURL: string; version: string; timeout: number; }> = { baseURL: "https://api.example.com", version: "v1", timeout: 5000 }; // API_CONFIG.timeout = 10000; // Error </code> ===== 15.2 属性选择工具类型 ===== ==== 15.2.1 Pick<T, K> - 选取属性 ==== 从类型 T 中选取一组属性 K 组成新类型。 <code typescript> type Pick<T, K extends keyof T> = { [P in K]: T[P]; }; // 使用示例 interface User { id: number; name: string; email: string; password: string; createdAt: Date; updatedAt: Date; } // 创建公开的用户信息类型(排除敏感字段) type PublicUser = Pick<User, "id" | "name" | "email">; // { // id: number; // name: string; // email: string; // } // API 响应类型 type UserResponse = Pick<User, "id" | "name" | "email" | "createdAt">; function getPublicUser(user: User): PublicUser { return { id: user.id, name: user.name, email: user.email }; } </code> ==== 15.2.2 Omit<T, K> - 排除属性 ==== 从类型 T 中排除一组属性 K 组成新类型。 <code typescript> type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>; // 使用示例 interface User { id: number; name: string; email: string; password: string; internalNotes: string; } // 排除敏感和内部字段 type SafeUser = Omit<User, "password" | "internalNotes">; // { // id: number; // name: string; // email: string; // } // 创建用户请求(排除自动生成的字段) type CreateUserRequest = Omit<User, "id">; // 更新用户请求(排除 id 和所有可选处理) type UpdateUserRequest = Partial<Omit<User, "id">>; // 实际应用:DTO 转换 class UserDTO { static toSafeUser(user: User): SafeUser { const { password, internalNotes, ...safeUser } = user; return safeUser; } } </code> ===== 15.3 联合类型操作工具 ===== ==== 15.3.1 Exclude<T, U> - 排除类型 ==== 从 T 中排除可以赋值给 U 的类型。 <code typescript> type Exclude<T, U> = T extends U ? never : T; // 使用示例 type AllStatus = "pending" | "success" | "error" | "loading"; type FinalStatus = Exclude<AllStatus, "pending" | "loading">; // "success" | "error" // 从联合类型中排除 null/undefined type MaybeString = string | null | undefined; type DefinitelyString = Exclude<MaybeString, null | undefined>; // string // 实际应用:状态机转换 type State = | { status: "idle" } | { status: "loading"; requestId: string } | { status: "success"; data: any } | { status: "error"; error: Error }; type NonIdleState = Exclude<State, { status: "idle" }>; // { status: "loading" } | { status: "success" } | { status: "error" } </code> ==== 15.3.2 Extract<T, U> - 提取类型 ==== 从 T 中提取可以赋值给 U 的类型。 <code typescript> type Extract<T, U> = T extends U ? T : never; // 使用示例 type AllEvents = "click" | "hover" | "scroll" | "resize" | "keydown"; type MouseEvents = Extract<AllEvents, "click" | "hover">; // "click" | "hover" // 提取特定类型的属性名 type StringProperties<T> = { [K in keyof T]: T[K] extends string ? K : never; }[keyof T]; interface Person { name: string; age: number; email: string; isActive: boolean; } type StringKeys = StringProperties<Person>; // "name" | "email" </code> ==== 15.3.3 NonNullable<T> - 非空类型 ==== 从 T 中排除 null 和 undefined。 <code typescript> type NonNullable<T> = T extends null | undefined ? never : T; // 使用示例 type MaybeNumber = number | null | undefined; type DefinitelyNumber = NonNullable<MaybeNumber>; // number // 实际应用:过滤数组中的空值 function filterNonNull<T>(items: (T | null | undefined)[]): T[] { return items.filter((item): item is T => item !== null && item !== undefined); } const maybeNumbers: (number | null)[] = [1, null, 2, null, 3]; const numbers = filterNonNull(maybeNumbers); // number[] </code> ===== 15.4 函数相关工具类型 ===== ==== 15.4.1 Parameters<T> - 提取参数类型 ==== 提取函数类型的参数类型,以元组形式返回。 <code typescript> type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never; // 使用示例 function createUser(name: string, email: string, age?: number): { id: number } { return { id: 1 }; } type CreateUserParams = Parameters<typeof createUser>; // [name: string, email: string, age?: number] // 提取第一个参数 type FirstParameter<T extends (...args: any) => any> = Parameters<T> extends [infer P, ...any[]] ? P : never; // 提取除第一个外的参数 type RestParameters<T extends (...args: any) => any> = Parameters<T> extends [any, ...infer P] ? P : never; // 实际应用:高阶函数类型安全 function withLogging<T extends (...args: any[]) => any>( fn: T ): (...args: Parameters<T>) => ReturnType<T> { return (...args) => { console.log("Calling function with:", args); return fn(...args); }; } const loggedCreateUser = withLogging(createUser); // 类型: (name: string, email: string, age?: number) => { id: number } </code> ==== 15.4.2 ReturnType<T> - 提取返回类型 ==== 提取函数类型的返回类型。 <code typescript> type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any; // 使用示例 async function fetchUser(id: number) { return { id, name: "Alice", email: "alice@example.com" }; } type FetchUserReturn = ReturnType<typeof fetchUser>; // Promise<{ id: number; name: string; email: string; }> // 提取 Promise 的解析类型 type Awaited<T> = T extends Promise<infer R> ? R : T; type UserData = Awaited<FetchUserReturn>; // { id: number; name: string; email: string; } // 实际应用:API 客户端类型 class ApiClient { async request<T>(url: string): Promise<T> { const response = await fetch(url); return response.json(); } } const client = new ApiClient(); type ApiRequestReturn<T> = ReturnType<ApiClient["request"]> extends Promise<infer R> ? R : never; </code> ==== 15.4.3 ThisParameterType<T> 和 OmitThisParameter<T> ==== 处理函数中的 this 参数。 <code typescript> // 提取 this 参数的类型 type ThisParameterType<T> = T extends (this: infer U, ...args: any[]) => any ? U : unknown; // 移除 this 参数 type OmitThisParameter<T> = unknown extends ThisParameterType<T> ? T : T extends (this: any, ...args: infer A) => infer R ? (...args: A) => R : T; // 使用示例 function greet(this: { name: string }, greeting: string): string { return `${greeting}, ${this.name}!`; } type GreetThis = ThisParameterType<typeof greet>; // { name: string } type GreetWithoutThis = OmitThisParameter<typeof greet>; // (greeting: string) => string </code> ===== 15.5 构造函数工具类型 ===== ==== 15.5.1 ConstructorParameters<T> - 构造参数 ==== 提取构造函数类型的参数类型。 <code typescript> type ConstructorParameters<T extends abstract new (...args: any) => any> = T extends abstract new (...args: infer P) => any ? P : never; // 使用示例 class User { constructor( public name: string, public age: number, public email?: string ) {} } type UserConstructorParams = ConstructorParameters<typeof User>; // [name: string, age: number, email?: string] // 实际应用:工厂函数 function createUser(...args: ConstructorParameters<typeof User>): User { return new User(...args); } const user = createUser("Alice", 25, "alice@example.com"); </code> ==== 15.5.2 InstanceType<T> - 实例类型 ==== 提取构造函数类型的实例类型。 <code typescript> type InstanceType<T extends abstract new (...args: any) => any> = T extends abstract new (...args: any) => infer R ? R : any; // 使用示例 type UserInstance = InstanceType<typeof User>; // User // 实际应用:依赖注入容器 type ClassConstructor<T = any> = new (...args: any[]) => T; class Container { private instances = new Map<string, any>(); register<T>(key: string, Constructor: ClassConstructor<T>): void { this.instances.set(key, new Constructor()); } get<T>(key: string): T { return this.instances.get(key); } } const container = new Container(); container.register("user", User); const userService = container.get<User>("user"); </code> ===== 15.6 字符串操作工具类型 ===== ==== 15.6.1 模板字面量工具类型(TypeScript 4.1+)===== <code typescript> // Uppercase - 转大写 type HTTPMethod = "get" | "post"; type UpperMethods = Uppercase<HTTPMethod>; // "GET" | "POST" // Lowercase - 转小写 type LowerMethods = Lowercase<"GET" | "POST">; // "get" | "post" // Capitalize - 首字母大写 type EventNames = "click" | "hover"; type HandlerNames = `on${Capitalize<EventNames>}`; // "onClick" | "onHover" // Uncapitalize - 首字母小写 type PropertyNames = "Name" | "Age"; type CamelCase = `get${PropertyNames}`; // "getName" | "getAge" </code> ==== 15.6.2 字符串解析类型 ==== <code typescript> // 提取事件名前缀 type EventPrefix<T extends string> = T extends `${infer Prefix}${string}` ? Prefix : never; type ClickPrefix = EventPrefix<"onClick">; // "on" // 提取 camelCase 的各个部分 type SplitCamelCase<S extends string> = S extends `${infer Head}${infer Tail}` ? Head extends Uppercase<Head> ? [Head, ...SplitCamelCase<Tail>] : [Head, ...SplitCamelCase<Tail>] : []; // 字符串路径提取 type ExtractPathParams<T extends string> = T extends `${string}:${infer Param}/${infer Rest}` ? { [K in Param]: string } & ExtractPathParams<`/${Rest}`> : T extends `${string}:${infer Param}` ? { [K in Param]: string } : {}; type RouteParams = ExtractPathParams<"/users/:id/posts/:postId">; // { id: string; postId: string; } </code> ===== 15.7 自定义工具类型 ===== ==== 15.7.1 深度工具类型 ==== <code typescript> // 深度 Partial type DeepPartial<T> = { [P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P]; }; // 深度 Required type DeepRequired<T> = { [P in keyof T]-?: T[P] extends object ? DeepRequired<T[P]> : T[P]; }; // 深度 Readonly type DeepReadonly<T> = { readonly [P in keyof T]: T[P] extends object ? DeepReadonly<T[P]> : T[P]; }; interface Nested { a: { b: { c: string; }; }; } type PartialNested = DeepPartial<Nested>; // { a?: { b?: { c?: string; }; }; } </code> ==== 15.7.2 键值操作工具类型 ==== <code typescript> // 获取指定类型的键 type KeysOfType<T, U> = { [K in keyof T]: T[K] extends U ? K : never; }[keyof T]; interface User { id: number; name: string; email: string; age: number; isActive: boolean; } type StringKeys = KeysOfType<User, string>; // "name" | "email" // 提取指定类型的属性 type PickByType<T, U> = Pick<T, KeysOfType<T, U>>; type StringProps = PickByType<User, string>; // { name: string; email: string; } // 排除指定类型的属性 type OmitByType<T, U> = Pick<T, Exclude<keyof T, KeysOfType<T, U>>>; type NonStringProps = OmitByType<User, string>; // { id: number; age: number; isActive: boolean; } // 可为 null 的属性 type Nullable<T> = { [P in keyof T]: T[P] | null }; // 移除 readonly type Mutable<T> = { -readonly [P in keyof T]: T[P] }; </code> ==== 15.7.3 联合类型工具 ==== <code typescript> // 联合类型转交叉类型 type UnionToIntersection<T> = (T extends any ? (x: T) => void : never) extends (x: infer R) => void ? R : never; type Mixed = { a: 1 } | { b: 2 }; type Intersection = UnionToIntersection<Mixed>; // { a: 1; b: 2; } // 联合类型转元组(保留顺序) type UnionToTuple<T, L = LastInUnion<T>> = [T] extends [never] ? [] : [...UnionToTuple<Exclude<T, L>>, L]; type LastInUnion<T> = UnionToIntersection<T extends any ? (x: T) => void : never> extends (x: infer L) => void ? L : never; type T = UnionToTuple<"a" | "b" | "c">; // ["a", "b", "c"] 或其他顺序 // 判断类型是否为 never type IsNever<T> = [T] extends [never] ? true : false; // 判断类型是否为 any type IsAny<T> = 0 extends (1 & T) ? true : false; </code> ===== 15.8 实战案例 ===== ==== 15.8.1 类型安全的 Redux 模式 ==== <code typescript> // Action 创建函数类型 interface Action<T extends string = string, P = any> { type: T; payload: P; } type ActionCreator<T extends string, P> = undefined extends P ? () => Action<T, P> : (payload: P) => Action<T, P>; // 从 action creators 对象推断 action 类型 type ActionsUnion<A extends Record<string, (...args: any[]) => Action>> = ReturnType<A[keyof A]>; // 使用 const userActions = { fetchUser: (id: number) => ({ type: "FETCH_USER" as const, payload: id }), updateUser: (user: { id: number; name: string }) => ({ type: "UPDATE_USER" as const, payload: user }), deleteUser: (id: number) => ({ type: "DELETE_USER" as const, payload: id }) }; type UserAction = ActionsUnion<typeof userActions>; // { type: "FETCH_USER"; payload: number; } | // { type: "UPDATE_USER"; payload: { id: number; name: string; }; } | // { type: "DELETE_USER"; payload: number; } </code> ==== 15.8.2 类型安全的 API 客户端 ==== <code typescript> // 端点定义 interface Endpoints { "/users": { GET: { response: User[] }; POST: { body: CreateUserDTO; response: User }; }; "/users/:id": { GET: { params: { id: string }; response: User }; PUT: { params: { id: string }; body: UpdateUserDTO; response: User }; DELETE: { params: { id: string }; response: void }; }; } // 提取路径参数 type ExtractParams<T extends string> = T extends `${string}:${infer Param}/${infer Rest}` ? { [K in Param]: string } & ExtractParams<`/${Rest}`> : T extends `${string}:${infer Param}` ? { [K in Param]: string } : {}; // 类型安全请求函数 type RequestMethod = "GET" | "POST" | "PUT" | "DELETE"; async function apiRequest< Path extends keyof Endpoints, Method extends keyof Endpoints[Path] >( path: Path, method: Method, ...args: Method extends "GET" ? Endpoints[Path][Method] extends { params: infer P } ? [P] : [] : Endpoints[Path][Method] extends { params: infer P; body: infer B } ? [P, B] : Endpoints[Path][Method] extends { body: infer B } ? [B] : [] ): Promise<Endpoints[Path][Method] extends { response: infer R } ? R : never> { // 实现... return null as any; } // 使用 const users = await apiRequest("/users", "GET"); const user = await apiRequest("/users/:id", "GET", { id: "1" }); </code> ===== 15.9 本章小结 ===== 本章我们学习了 TypeScript 的内置工具类型: 1. **Partial<T>** - 所有属性可选 2. **Required<T>** - 所有属性必需 3. **Readonly<T>** - 所有属性只读 4. **Pick<T, K>** - 选取指定属性 5. **Omit<T, K>** - 排除指定属性 6. **Exclude<T, U>** - 从联合类型排除 7. **Extract<T, U>** - 从联合类型提取 8. **NonNullable<T>** - 排除 null 和 undefined 9. **Parameters<T>** - 提取函数参数 10. **ReturnType<T>** - 提取函数返回类型 11. **ConstructorParameters<T>** - 提取构造函数参数 12. **InstanceType<T>** - 提取实例类型 13. **字符串工具类型** - Uppercase、Lowercase、Capitalize、Uncapitalize 以及自定义工具类型的创建方法。 ===== 15.10 练习题 ===== ==== 练习 1:基础工具类型应用 ==== 给定以下接口,创建相应的 DTO 类型: <code typescript> interface Product { id: string; name: string; price: number; description: string; createdAt: Date; updatedAt: Date; internalNotes: string; } // 1. 创建产品请求(排除 id、时间戳和内部字段) // 2. 更新产品请求(所有字段可选,排除 id) // 3. 公开产品信息(只包含 name、price、description) // 4. 产品列表项(公开信息 + id) </code> 参考答案 <code typescript> // 1. 创建产品请求 type CreateProductDTO = Omit<Product, "id" | "createdAt" | "updatedAt" | "internalNotes">; // 2. 更新产品请求 type UpdateProductDTO = Partial<Omit<Product, "id" | "createdAt" | "updatedAt" | "internalNotes">>; // 3. 公开产品信息 type PublicProduct = Pick<Product, "name" | "price" | "description">; // 4. 产品列表项 type ProductListItem = Pick<Product, "id" | "name" | "price" | "description">; // 或者使用组合 type ProductListItem2 = Pick<Product, "id"> & PublicProduct; </code> ==== 练习 2:自定义工具类型 ==== 实现以下工具类型: <code typescript> // 1. 将对象的所有属性路径展平为联合类型 // 例如:{ a: { b: number; c: string } } => "a" | "a.b" | "a.c" type Paths<T> = any; // 2. 根据路径获取对象类型 type PathValue<T, P extends string> = any; // 3. 创建响应式引用类型(类似 Vue 的 Ref) type Ref<T> = any; </code> 参考答案 <code typescript> // 1. 属性路径 type Paths<T, K extends keyof T = keyof T> = K extends string ? T[K] extends Record<string, any> ? K | `${K}.${Paths<T[K]>}` : K : never; interface Nested { user: { name: string; address: { city: string; }; }; } type NestedPaths = Paths<Nested>; // "user" | "user.name" | "user.address" | "user.address.city" // 2. 路径值获取 type PathValue<T, P extends string> = P extends `${infer K}.${infer Rest}` ? K extends keyof T ? PathValue<T[K], Rest> : never : P extends keyof T ? T[P] : never; type City = PathValue<Nested, "user.address.city">; // string // 3. 响应式引用 type Ref<T> = { value: T }; function ref<T>(value: T): Ref<T> { return { value }; } const count = ref(0); // Ref<number> const user = ref({ name: "Alice" }); // Ref<{ name: string; }> </code> ==== 练习 3:类型安全的表单验证 ==== 使用工具类型实现类型安全的表单验证: <code typescript> // 要求: // 1. 根据数据模型自动生成验证规则类型 // 2. 验证结果类型包含每个字段的错误信息 // 3. 验证函数返回类型安全的结果 </code> 参考答案 <code typescript> // 验证规则 interface ValidationRule<T> { required?: boolean; min?: number; max?: number; pattern?: RegExp; custom?: (value: T) => string | undefined; } // 验证器映射 type ValidatorMap<T> = { [K in keyof T]?: ValidationRule<T[K]>; }; // 验证错误 type ValidationErrors<T> = { [K in keyof T]?: string; }; // 验证结果 type ValidationResult<T> = | { valid: true; errors: null } | { valid: false; errors: ValidationErrors<T> }; // 验证器类 class FormValidator<T extends Record<string, any>> { constructor(private rules: ValidatorMap<T>) {} validate(data: T): ValidationResult<T> { const errors: ValidationErrors<T> = {}; for (const key in this.rules) { const rule = this.rules[key]; const value = data[key]; if (!rule) continue; // 必填验证 if (rule.required && (value === undefined || value === null || value === "")) { errors[key] = `${String(key)} is required`; continue; } // 数值验证 if (typeof value === "number") { if (rule.min !== undefined && value < rule.min) { errors[key] = `${String(key)} must be at least ${rule.min}`; } if (rule.max !== undefined && value > rule.max) { errors[key] = `${String(key)} must be at most ${rule.max}`; } } // 字符串验证 if (typeof value === "string") { if (rule.pattern && !rule.pattern.test(value)) { errors[key] = `${String(key)} format is invalid`; } } // 自定义验证 if (rule.custom) { const error = rule.custom(value); if (error) { errors[key] = error; } } } const isValid = Object.keys(errors).length === 0; return isValid ? { valid: true, errors: null } : { valid: false, errors }; } } // 使用 interface LoginForm { email: string; password: string; rememberMe: boolean; } const loginValidator = new FormValidator<LoginForm>({ email: { required: true, pattern: /^[^\s@]+@[^\s@]+\.[^\s@]+$/ }, password: { required: true, custom: (value) => value.length < 8 ? "Password must be at least 8 characters" : undefined } }); const result = loginValidator.validate({ email: "invalid", password: "short", rememberMe: true }); if (!result.valid) { console.log(result.errors.email); // string | undefined console.log(result.errors.password); // string | undefined } </code> ===== 扩展阅读 ===== - [[TypeScript:第二十章_项目实战|项目实战章节]] - [[https://www.typescriptlang.org/docs/handbook/utility-types.html|TypeScript 官方文档 - 工具类型]] - [[https://github.com/sindresorhus/type-fest|type-fest: 高级工具类型库]] 登录 Detach Close 该主题尚不存在 您访问的页面并不存在。如果允许,您可以使用创建该页面按钮来创建它。 typescript/第十五章_工具类型.txt 最后更改: 2026/03/09 15:26由 张叶安 登录