====== 第十四章:类型操作符 ====== ===== 本章概述 ===== TypeScript 提供了丰富的类型操作符,使我们能够灵活地查询、转换和操作类型。本章将详细介绍 keyof、typeof、in、is 等查询操作符,以及条件类型、映射类型等高级类型操作技术。掌握这些操作符,将使你能够编写出更加灵活和强大的类型定义。 ===== 14.1 keyof 操作符 ===== ==== 14.1.1 基础用法 ==== keyof 操作符用于获取类型的所有键组成的联合类型。 interface Person { name: string; age: number; email: string; } type PersonKeys = keyof Person; // 等价于: "name" | "age" | "email" // 使用场景:类型安全的属性访问 function getProperty(obj: T, key: K): T[K] { return obj[key]; } const person: Person = { name: "Alice", age: 25, email: "alice@example.com" }; const name = getProperty(person, "name"); // string 类型 const age = getProperty(person, "age"); // number 类型 // const invalid = getProperty(person, "gender"); // Error: "gender" 不是 Person 的键 ==== 14.1.2 keyof 与索引签名 ==== // 字符串索引签名 interface StringDictionary { [key: string]: string; } type StringKeys = keyof StringDictionary; // string | number // 数字索引签名 interface NumberDictionary { [key: number]: string; } type NumberKeys = keyof NumberDictionary; // number // 结合使用 interface MixedDictionary { [key: string]: string | number; length: number; // 必须兼容索引签名 } type MixedKeys = keyof MixedDictionary; // string | number | "length" ==== 14.1.3 实战应用:pick 函数 ==== // 实现类型安全的对象选择 function pick(obj: T, keys: K[]): Pick { const result = {} as Pick; keys.forEach(key => { result[key] = obj[key]; }); return result; } interface User { id: number; name: string; email: string; password: string; createdAt: Date; } const user: User = { id: 1, name: "Alice", email: "alice@example.com", password: "secret", createdAt: new Date() }; // 只选择安全显示的字段 const publicUser = pick(user, ["id", "name", "email"]); // 类型: { id: number; name: string; email: string } ===== 14.2 typeof 操作符 ===== ==== 14.2.1 类型上下文中的 typeof ==== 在类型上下文中,typeof 用于获取变量或表达式的类型。 // 获取对象类型 const person = { name: "Alice", age: 25, hobbies: ["reading", "coding"] }; type PersonType = typeof person; // { // name: string; // age: number; // hobbies: string[]; // } // 获取函数类型 function greet(name: string, greeting?: string): string { return `${greeting ?? "Hello"}, ${name}!`; } type GreetFunction = typeof greet; // (name: string, greeting?: string | undefined) => string // 获取类实例类型 class User { constructor(public name: string, public age: number) {} } type UserInstance = typeof User.prototype; // User type UserConstructor = typeof User; // new (name: string, age: number) => User ==== 14.2.2 typeof 与 const 断言结合 ==== // 使用 const 断言获得更精确的类型 const config = { api: { endpoint: "/api/v1", timeout: 5000 }, features: { darkMode: true, notifications: false } } as const; type ConfigType = typeof config; // { // readonly api: { // readonly endpoint: "/api/v1"; // readonly timeout: 5000; // }; // readonly features: { // readonly darkMode: true; // readonly notifications: false; // }; // } // 提取嵌套类型 type ApiConfig = typeof config.api; // { readonly endpoint: "/api/v1"; readonly timeout: 5000; } ==== 14.2.3 实战:从运行时对象创建类型 ==== // 运行时配置对象 const API_ENDPOINTS = { users: "/api/users", posts: "/api/posts", comments: "/api/comments" } as const; // 从运行时值创建类型 type EndpointPaths = typeof API_ENDPOINTS[keyof typeof API_ENDPOINTS]; // "/api/users" | "/api/posts" | "/api/comments" // 类型安全的 API 客户端 async function fetchApi( path: EndpointPaths, options?: RequestInit ): Promise { const response = await fetch(path, options); return response.json(); } // 使用 interface User { id: number; name: string; } const users = await fetchApi(API_ENDPOINTS.users); ===== 14.3 in 操作符(类型中)===== ==== 14.3.1 映射类型中的 in ==== in 操作符用于遍历联合类型的每个成员,常用于创建映射类型。 // 基础映射类型 - 将所有属性变为只读 type Readonly = { readonly [P in keyof T]: T[P]; }; interface User { name: string; age: number; } type ReadonlyUser = Readonly; // { readonly name: string; readonly age: number; } // 将所有属性变为可选 type Partial = { [P in keyof T]?: T[P]; }; // 将所有属性变为必需 type Required = { [P in keyof T]-?: T[P]; // -? 移除可选修饰符 }; // 只读数组类型 type ReadonlyArray = { readonly [P in keyof T[]]: T[P]; }; ==== 14.3.2 重映射键名(TypeScript 4.1+)===== // 使用 as 重映射键名 type Getters = { [K in keyof T as `get${Capitalize}`]: () => T[K]; }; interface Person { name: string; age: number; } type PersonGetters = Getters; // { // getName: () => string; // getAge: () => number; // } // 过滤特定类型的属性 type StringKeys = { [K in keyof T as T[K] extends string ? K : never]: T[K]; }; interface Mixed { name: string; age: number; email: string; active: boolean; } type OnlyStrings = StringKeys; // { name: string; email: string; } ==== 14.3.3 实战:创建事件处理器类型 ==== // 根据属性创建对应的事件处理器类型 type EventHandlers = { [K in keyof T as `on${Capitalize}Change`]?: ( newValue: T[K], oldValue: T[K] ) => void; }; interface Settings { theme: "light" | "dark"; fontSize: number; notifications: boolean; } type SettingsEventHandlers = EventHandlers; // { // onThemeChange?: (newValue: "light" | "dark", oldValue: "light" | "dark") => void; // onFontSizeChange?: (newValue: number, oldValue: number) => void; // onNotificationsChange?: (newValue: boolean, oldValue: boolean) => void; // } // 实现带有事件处理器的类 class ObservableSettings implements Settings { theme: "light" | "dark" = "light"; fontSize: number = 14; notifications: boolean = true; private handlers: EventHandlers = {}; on>( event: K, handler: EventHandlers[K] ): void { this.handlers[event] = handler; } set(key: K, value: Settings[K]): void { const oldValue = this[key]; this[key] = value; const handlerName = `on${key.charAt(0).toUpperCase() + key.slice(1)}Change` as const; const handler = this.handlers[handlerName]; if (handler) { handler(value, oldValue); } } } ===== 14.4 is 操作符(类型保护)===== ==== 14.4.1 自定义类型保护 ==== is 关键字用于创建返回 boolean 的自定义类型保护函数。 // 基本类型保护 function isString(value: unknown): value is string { return typeof value === "string"; } function isNumber(value: unknown): value is number { return typeof value === "number"; } function processValue(value: unknown) { if (isString(value)) { // value 被缩小为 string 类型 console.log(value.toUpperCase()); } else if (isNumber(value)) { // value 被缩小为 number 类型 console.log(value.toFixed(2)); } } ==== 14.4.2 复杂类型保护 ==== // 接口定义 interface Cat { type: "cat"; name: string; meow: () => void; } interface Dog { type: "dog"; name: string; bark: () => void; } interface Bird { type: "bird"; name: string; chirp: () => void; } type Animal = Cat | Dog | Bird; // 类型保护函数 function isCat(animal: Animal): animal is Cat { return animal.type === "cat"; } function isDog(animal: Animal): animal is Dog { return animal.type === "dog"; } function isBird(animal: Animal): animal is Bird { return animal.type === "bird"; } // 使用类型保护 function makeSound(animal: Animal): void { if (isCat(animal)) { animal.meow(); } else if (isDog(animal)) { animal.bark(); } else if (isBird(animal)) { animal.chirp(); } } ==== 14.4.3 泛型类型保护 ==== // 检查对象是否有某个属性 function hasProperty( obj: unknown, prop: T ): obj is { [K in T]: unknown } { return typeof obj === "object" && obj !== null && prop in obj; } // 检查是否为 Promise function isPromise(value: unknown): value is Promise { return value instanceof Promise || ( typeof value === "object" && value !== null && "then" in value && typeof (value as Promise).then === "function" ); } // 使用 async function resolveValue(value: T | Promise): Promise { if (isPromise(value)) { return value; } return value; } ===== 14.5 条件类型 ===== ==== 14.5.1 基础语法 ==== 条件类型根据类型关系选择两种类型之一,语法类似三元运算符。 // 基本语法: T extends U ? X : Y type IsString = T extends string ? true : false; type T1 = IsString; // true type T2 = IsString; // false type T3 = IsString<"hello">; // true (字面量类型是 string 的子类型) // 检查是否继承自某个类型 class Animal { name!: string; } class Dog extends Animal { bark() {} } class Car { brand!: string; } type IsAnimal = T extends Animal ? true : false; type A = IsAnimal; // true type B = IsAnimal; // false ==== 14.5.2 分布式条件类型 ==== 当条件类型作用于泛型参数时,会自动分布到联合类型的每个成员。 // 分布式条件类型 type ToArray = T extends any ? T[] : never; // 对联合类型进行分布 type StringOrNumberArray = ToArray; // string[] | number[] (不是 (string | number)[]) // 防止分布式行为 - 使用元组包裹 type NonDistributed = [T] extends [any] ? T[] : never; type A = NonDistributed; // (string | number)[] // 实用示例:过滤联合类型 type Exclude = T extends U ? never : T; type Extract = T extends U ? T : never; type T = Exclude<"a" | "b" | "c", "a">; // "b" | "c" type U = Extract<"a" | "b" | "c", "a" | "d">; // "a" ==== 14.5.3 infer 关键字 ==== infer 用于在条件类型中推断类型变量。 // 提取函数返回类型 type ReturnType = T extends (...args: any[]) => infer R ? R : never; function getUser() { return { id: 1, name: "Alice" }; } type User = ReturnType; // { id: number; name: string; } // 提取函数参数类型 type Parameters = T extends (...args: infer P) => any ? P : never; function updateUser(id: number, data: { name: string }) {} type UpdateUserParams = Parameters; // [number, { name: string }] // 提取数组元素类型 type ElementType = T extends (infer E)[] ? E : T; type A = ElementType; // string type B = ElementType; // number[] // 提取 Promise 返回值 type Awaited = T extends Promise ? R : T; type P = Awaited>; // string type N = Awaited; // number ===== 14.6 映射类型 ===== ==== 14.6.1 基础映射类型 ==== // 只读类型 type Readonly = { readonly [P in keyof T]: T[P]; }; // 可选类型 type Partial = { [P in keyof T]?: T[P]; }; // 必需类型(移除可选) type Required = { [P in keyof T]-?: T[P]; }; // 可空类型 type Nullable = { [P in keyof T]: T[P] | null; }; // 代理包装 type Proxy = { [P in keyof T]: { get(): T[P]; set(value: T[P]): void }; }; interface Person { name: string; age: number; } type ProxiedPerson = Proxy; // { // name: { get(): string; set(value: string): void; }; // age: { get(): number; set(value: number): void; }; // } ==== 14.6.2 深度映射类型 ==== // 深度只读 type DeepReadonly = { readonly [P in keyof T]: T[P] extends object ? DeepReadonly : T[P]; }; // 深度可选 type DeepPartial = { [P in keyof T]?: T[P] extends object ? DeepPartial : T[P]; }; // 深度必需 type DeepRequired = { [P in keyof T]-?: T[P] extends object ? DeepRequired : T[P]; }; interface Nested { a: { b: { c: string; }; d: number; }; } type ReadonlyNested = DeepReadonly; // { // readonly a: { // readonly b: { // readonly c: string; // }; // readonly d: number; // }; // } ===== 14.7 模板字面量类型 ===== ==== 14.7.1 基础模板字面量 ==== // 字符串字面量拼接 type EventName = `on${Capitalize}`; type ClickEvent = EventName<"click">; // "onClick" type HoverEvent = EventName<"hover">; // "onHover" // 组合多个类型 type Position = "top" | "bottom" | "left" | "right"; type Margin = `margin${Capitalize}`; // "marginTop" | "marginBottom" | "marginLeft" | "marginRight" // 嵌套模板 type HttpMethod = "get" | "post" | "put" | "delete"; type Endpoint = `/api/${string}`; type ApiUrl = `${Uppercase} ${Endpoint}`; ==== 14.7.2 模板字面量的模式匹配 ==== // 提取事件名 type GetEvent = T extends `on${infer Event}` ? Uncapitalize : never; type E1 = GetEvent<"onClick">; // "click" type E2 = GetEvent<"onMouseEnter">; // "mouseEnter" type E3 = GetEvent<"foo">; // never // 提取 CSS 变量 type ExtractVar = T extends `var(--${infer Var})` ? Var : never; type V1 = ExtractVar<"var(--primary-color)">; // "primary-color" type V2 = ExtractVar<"var(--spacing-md)">; // "spacing-md" // 复杂模式匹配 type ParseRoute = T extends `${infer Method} /api/${infer Path}` ? { method: Method; path: Path } : never; type Route = ParseRoute<"GET /api/users">; // { method: "GET"; path: "users"; } ===== 14.8 实用类型组合 ===== ==== 14.8.1 组合多个操作符 ==== // 提取对象的可选属性名 type OptionalKeys = { [K in keyof T]-?: {} extends Pick ? K : never; }[keyof T]; interface User { id: number; name: string; email?: string; age?: number; } type UserOptionalKeys = OptionalKeys; // "email" | "age" // 提取对象的可选属性 type OptionalProps = Pick>; type UserOptional = OptionalProps; // { email?: string; age?: number; } // 扁平化嵌套对象(只支持一级) type Flatten = { [K in keyof T]: T[K]; }; ==== 14.8.2 递归类型操作 ==== // 路径字符串生成 type Path = K extends string ? T[K] extends Record ? `${K}.${Path}` | K : K : never; interface Data { user: { name: string; address: { city: string; country: string; }; }; settings: { theme: string; }; } type DataPaths = Path; // "user" | "user.name" | "user.address" | "user.address.city" | ... ===== 14.9 本章小结 ===== 本章我们学习了: 1. **keyof** - 获取类型的所有键 2. **typeof** - 获取值的类型 3. **in** - 遍历联合类型,创建映射类型 4. **is** - 自定义类型保护函数 5. **条件类型** - 根据类型关系选择类型 6. **infer** - 在条件类型中推断类型 7. **映射类型** - 转换现有类型的属性 8. **模板字面量类型** - 字符串类型的模式操作 ===== 14.10 练习题 ===== ==== 练习 1:keyof 应用 ==== 实现一个函数,能够安全地获取对象的多个属性: function pluck(obj: T, keys: K[]): Pick { // 实现 } interface Product { id: number; name: string; price: number; category: string; } const product: Product = { id: 1, name: "Laptop", price: 999, category: "Electronics" }; // 期望:提取 id 和 name const summary = pluck(product, ["id", "name"]); // 类型: { id: number; name: string; } 参考答案 function pluck(obj: T, keys: K[]): Pick { const result = {} as Pick; keys.forEach(key => { result[key] = obj[key]; }); return result; } // 或者使用 reduce function pluck2(obj: T, keys: K[]): Pick { return keys.reduce((acc, key) => { acc[key] = obj[key]; return acc; }, {} as Pick); } // 使用 const summary = pluck(product, ["id", "name"]); console.log(summary); // { id: 1, name: "Laptop" } ==== 练习 2:类型保护实现 ==== 实现以下类型和类型保护函数: interface Square { kind: "square"; size: number; } interface Rectangle { kind: "rectangle"; width: number; height: number; } interface Circle { kind: "circle"; radius: number; } type Shape = Square | Rectangle | Circle; // 实现类型保护函数和计算面积函数 参考答案 // 类型保护函数 function isSquare(shape: Shape): shape is Square { return shape.kind === "square"; } function isRectangle(shape: Shape): shape is Rectangle { return shape.kind === "rectangle"; } function isCircle(shape: Shape): shape is Circle { return shape.kind === "circle"; } // 计算面积 function calculateArea(shape: Shape): number { if (isSquare(shape)) { return shape.size ** 2; } else if (isRectangle(shape)) { return shape.width * shape.height; } else if (isCircle(shape)) { return Math.PI * shape.radius ** 2; } // 穷尽检查 const _exhaustive: never = shape; return _exhaustive; } // 使用 const shapes: Shape[] = [ { kind: "square", size: 5 }, { kind: "rectangle", width: 4, height: 6 }, { kind: "circle", radius: 3 } ]; shapes.forEach(shape => { console.log(`${shape.kind} area:`, calculateArea(shape)); }); ==== 练习 3:条件类型与 infer ==== 实现以下类型工具: // 1. 提取数组的嵌套层级深度 type ArrayDepth = any; // 2. 提取函数的第 N 个参数类型 type NthParameter = any; // 3. 将联合类型转换为交叉类型 type UnionToIntersection = any; 参考答案 // 1. 数组嵌套深度 type ArrayDepth = T extends (infer E)[] ? ArrayDepth : Depth["length"]; type D1 = ArrayDepth; // 0 type D2 = ArrayDepth; // 1 type D3 = ArrayDepth; // 2 // 2. 第 N 个参数 type NthParameter = T extends (...args: infer P) => any ? P[N] : never; function example(a: string, b: number, c: boolean) {} type P1 = NthParameter; // string type P2 = NthParameter; // number // 3. 联合转交叉 type UnionToIntersection = (T extends any ? (x: T) => void : never) extends (x: infer R) => void ? R : never; type U2I = UnionToIntersection<{ a: 1 } | { b: 2 }>; // { a: 1 } & { b: 2 } ==== 练习 4:映射类型实战 ==== 实现一个类型安全的表单验证系统: // 要求: // 1. 根据数据模型自动生成验证规则类型 // 2. 支持必填、最小长度、正则等验证规则 // 3. 验证函数返回详细的错误信息 参考答案 // 验证规则类型 interface ValidationRule { required?: boolean; minLength?: number; maxLength?: number; pattern?: RegExp; validator?: (value: T) => string | undefined; } // 字段验证器 type FieldValidators = { [K in keyof T]?: ValidationRule; }; // 验证错误 type ValidationErrors = { [K in keyof T]?: string; }; // 验证器类 class Validator> { constructor(private rules: FieldValidators) {} validate(data: T): { valid: boolean; errors: ValidationErrors } { const errors: ValidationErrors = {}; 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 === "string") { if (rule.minLength !== undefined && value.length < rule.minLength) { errors[key] = `${String(key)} must be at least ${rule.minLength} characters`; continue; } if (rule.maxLength !== undefined && value.length > rule.maxLength) { errors[key] = `${String(key)} must be at most ${rule.maxLength} characters`; continue; } if (rule.pattern && !rule.pattern.test(value)) { errors[key] = `${String(key)} format is invalid`; continue; } } // 自定义验证 if (rule.validator) { const error = rule.validator(value); if (error) { errors[key] = error; } } } return { valid: Object.keys(errors).length === 0, errors }; } } // 使用示例 interface UserForm { username: string; email: string; age: number; } const userValidator = new Validator({ username: { required: true, minLength: 3, maxLength: 20 }, email: { required: true, pattern: /^[^\s@]+@[^\s@]+\.[^\s@]+$/ }, age: { required: true, validator: (value) => value < 18 ? "Must be at least 18" : undefined } }); // 验证 const result1 = userValidator.validate({ username: "ab", email: "invalid-email", age: 16 }); // result1.valid = false // result1.errors = { username: "...", email: "...", age: "..." } const result2 = userValidator.validate({ username: "alice", email: "alice@example.com", age: 25 }); // result2.valid = true ===== 扩展阅读 ===== - [[TypeScript:第十五章_工具类型|下一章:工具类型]] - [[https://www.typescriptlang.org/docs/handbook/2/keyof-types.html|TypeScript 官方文档 - keyof 类型操作符]] - [[https://www.typescriptlang.org/docs/handbook/2/conditional-types.html|TypeScript 官方文档 - 条件类型]]