显示页面讨论过去修订反向链接回到顶部 本页面只读。您可以查看源文件,但不能更改它。如果您觉得这是系统错误,请联系管理员。 ====== 第十四章:类型操作符 ====== ===== 本章概述 ===== TypeScript 提供了丰富的类型操作符,使我们能够灵活地查询、转换和操作类型。本章将详细介绍 keyof、typeof、in、is 等查询操作符,以及条件类型、映射类型等高级类型操作技术。掌握这些操作符,将使你能够编写出更加灵活和强大的类型定义。 ===== 14.1 keyof 操作符 ===== ==== 14.1.1 基础用法 ==== keyof 操作符用于获取类型的所有键组成的联合类型。 <code typescript> interface Person { name: string; age: number; email: string; } type PersonKeys = keyof Person; // 等价于: "name" | "age" | "email" // 使用场景:类型安全的属性访问 function getProperty<T, K extends keyof T>(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 的键 </code> ==== 14.1.2 keyof 与索引签名 ==== <code typescript> // 字符串索引签名 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" </code> ==== 14.1.3 实战应用:pick 函数 ==== <code typescript> // 实现类型安全的对象选择 function pick<T, K extends keyof T>(obj: T, keys: K[]): Pick<T, K> { const result = {} as Pick<T, K>; 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 } </code> ===== 14.2 typeof 操作符 ===== ==== 14.2.1 类型上下文中的 typeof ==== 在类型上下文中,typeof 用于获取变量或表达式的类型。 <code typescript> // 获取对象类型 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 </code> ==== 14.2.2 typeof 与 const 断言结合 ==== <code typescript> // 使用 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; } </code> ==== 14.2.3 实战:从运行时对象创建类型 ==== <code typescript> // 运行时配置对象 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<T>( path: EndpointPaths, options?: RequestInit ): Promise<T> { const response = await fetch(path, options); return response.json(); } // 使用 interface User { id: number; name: string; } const users = await fetchApi<User[]>(API_ENDPOINTS.users); </code> ===== 14.3 in 操作符(类型中)===== ==== 14.3.1 映射类型中的 in ==== in 操作符用于遍历联合类型的每个成员,常用于创建映射类型。 <code typescript> // 基础映射类型 - 将所有属性变为只读 type Readonly<T> = { readonly [P in keyof T]: T[P]; }; interface User { name: string; age: number; } type ReadonlyUser = Readonly<User>; // { readonly name: string; readonly age: number; } // 将所有属性变为可选 type Partial<T> = { [P in keyof T]?: T[P]; }; // 将所有属性变为必需 type Required<T> = { [P in keyof T]-?: T[P]; // -? 移除可选修饰符 }; // 只读数组类型 type ReadonlyArray<T> = { readonly [P in keyof T[]]: T[P]; }; </code> ==== 14.3.2 重映射键名(TypeScript 4.1+)===== <code typescript> // 使用 as 重映射键名 type Getters<T> = { [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K]; }; interface Person { name: string; age: number; } type PersonGetters = Getters<Person>; // { // getName: () => string; // getAge: () => number; // } // 过滤特定类型的属性 type StringKeys<T> = { [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<Mixed>; // { name: string; email: string; } </code> ==== 14.3.3 实战:创建事件处理器类型 ==== <code typescript> // 根据属性创建对应的事件处理器类型 type EventHandlers<T> = { [K in keyof T as `on${Capitalize<string & K>}Change`]?: ( newValue: T[K], oldValue: T[K] ) => void; }; interface Settings { theme: "light" | "dark"; fontSize: number; notifications: boolean; } type SettingsEventHandlers = EventHandlers<Settings>; // { // 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<Settings> = {}; on<K extends keyof EventHandlers<Settings>>( event: K, handler: EventHandlers<Settings>[K] ): void { this.handlers[event] = handler; } set<K extends keyof Settings>(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); } } } </code> ===== 14.4 is 操作符(类型保护)===== ==== 14.4.1 自定义类型保护 ==== is 关键字用于创建返回 boolean 的自定义类型保护函数。 <code typescript> // 基本类型保护 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)); } } </code> ==== 14.4.2 复杂类型保护 ==== <code typescript> // 接口定义 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(); } } </code> ==== 14.4.3 泛型类型保护 ==== <code typescript> // 检查对象是否有某个属性 function hasProperty<T extends string>( obj: unknown, prop: T ): obj is { [K in T]: unknown } { return typeof obj === "object" && obj !== null && prop in obj; } // 检查是否为 Promise function isPromise<T>(value: unknown): value is Promise<T> { return value instanceof Promise || ( typeof value === "object" && value !== null && "then" in value && typeof (value as Promise<T>).then === "function" ); } // 使用 async function resolveValue<T>(value: T | Promise<T>): Promise<T> { if (isPromise<T>(value)) { return value; } return value; } </code> ===== 14.5 条件类型 ===== ==== 14.5.1 基础语法 ==== 条件类型根据类型关系选择两种类型之一,语法类似三元运算符。 <code typescript> // 基本语法: T extends U ? X : Y type IsString<T> = T extends string ? true : false; type T1 = IsString<string>; // true type T2 = IsString<number>; // false type T3 = IsString<"hello">; // true (字面量类型是 string 的子类型) // 检查是否继承自某个类型 class Animal { name!: string; } class Dog extends Animal { bark() {} } class Car { brand!: string; } type IsAnimal<T> = T extends Animal ? true : false; type A = IsAnimal<Dog>; // true type B = IsAnimal<Car>; // false </code> ==== 14.5.2 分布式条件类型 ==== 当条件类型作用于泛型参数时,会自动分布到联合类型的每个成员。 <code typescript> // 分布式条件类型 type ToArray<T> = T extends any ? T[] : never; // 对联合类型进行分布 type StringOrNumberArray = ToArray<string | number>; // string[] | number[] (不是 (string | number)[]) // 防止分布式行为 - 使用元组包裹 type NonDistributed<T> = [T] extends [any] ? T[] : never; type A = NonDistributed<string | number>; // (string | number)[] // 实用示例:过滤联合类型 type Exclude<T, U> = T extends U ? never : T; type Extract<T, U> = T extends U ? T : never; type T = Exclude<"a" | "b" | "c", "a">; // "b" | "c" type U = Extract<"a" | "b" | "c", "a" | "d">; // "a" </code> ==== 14.5.3 infer 关键字 ==== infer 用于在条件类型中推断类型变量。 <code typescript> // 提取函数返回类型 type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never; function getUser() { return { id: 1, name: "Alice" }; } type User = ReturnType<typeof getUser>; // { id: number; name: string; } // 提取函数参数类型 type Parameters<T> = T extends (...args: infer P) => any ? P : never; function updateUser(id: number, data: { name: string }) {} type UpdateUserParams = Parameters<typeof updateUser>; // [number, { name: string }] // 提取数组元素类型 type ElementType<T> = T extends (infer E)[] ? E : T; type A = ElementType<string[]>; // string type B = ElementType<number[][]>; // number[] // 提取 Promise 返回值 type Awaited<T> = T extends Promise<infer R> ? R : T; type P = Awaited<Promise<string>>; // string type N = Awaited<number>; // number </code> ===== 14.6 映射类型 ===== ==== 14.6.1 基础映射类型 ==== <code typescript> // 只读类型 type Readonly<T> = { readonly [P in keyof T]: T[P]; }; // 可选类型 type Partial<T> = { [P in keyof T]?: T[P]; }; // 必需类型(移除可选) type Required<T> = { [P in keyof T]-?: T[P]; }; // 可空类型 type Nullable<T> = { [P in keyof T]: T[P] | null; }; // 代理包装 type Proxy<T> = { [P in keyof T]: { get(): T[P]; set(value: T[P]): void }; }; interface Person { name: string; age: number; } type ProxiedPerson = Proxy<Person>; // { // name: { get(): string; set(value: string): void; }; // age: { get(): number; set(value: number): void; }; // } </code> ==== 14.6.2 深度映射类型 ==== <code typescript> // 深度只读 type DeepReadonly<T> = { readonly [P in keyof T]: T[P] extends object ? DeepReadonly<T[P]> : T[P]; }; // 深度可选 type DeepPartial<T> = { [P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P]; }; // 深度必需 type DeepRequired<T> = { [P in keyof T]-?: T[P] extends object ? DeepRequired<T[P]> : T[P]; }; interface Nested { a: { b: { c: string; }; d: number; }; } type ReadonlyNested = DeepReadonly<Nested>; // { // readonly a: { // readonly b: { // readonly c: string; // }; // readonly d: number; // }; // } </code> ===== 14.7 模板字面量类型 ===== ==== 14.7.1 基础模板字面量 ==== <code typescript> // 字符串字面量拼接 type EventName<T extends string> = `on${Capitalize<T>}`; type ClickEvent = EventName<"click">; // "onClick" type HoverEvent = EventName<"hover">; // "onHover" // 组合多个类型 type Position = "top" | "bottom" | "left" | "right"; type Margin = `margin${Capitalize<Position>}`; // "marginTop" | "marginBottom" | "marginLeft" | "marginRight" // 嵌套模板 type HttpMethod = "get" | "post" | "put" | "delete"; type Endpoint = `/api/${string}`; type ApiUrl = `${Uppercase<HttpMethod>} ${Endpoint}`; </code> ==== 14.7.2 模板字面量的模式匹配 ==== <code typescript> // 提取事件名 type GetEvent<T extends string> = T extends `on${infer Event}` ? Uncapitalize<Event> : never; type E1 = GetEvent<"onClick">; // "click" type E2 = GetEvent<"onMouseEnter">; // "mouseEnter" type E3 = GetEvent<"foo">; // never // 提取 CSS 变量 type ExtractVar<T extends string> = 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 string> = T extends `${infer Method} /api/${infer Path}` ? { method: Method; path: Path } : never; type Route = ParseRoute<"GET /api/users">; // { method: "GET"; path: "users"; } </code> ===== 14.8 实用类型组合 ===== ==== 14.8.1 组合多个操作符 ==== <code typescript> // 提取对象的可选属性名 type OptionalKeys<T> = { [K in keyof T]-?: {} extends Pick<T, K> ? K : never; }[keyof T]; interface User { id: number; name: string; email?: string; age?: number; } type UserOptionalKeys = OptionalKeys<User>; // "email" | "age" // 提取对象的可选属性 type OptionalProps<T> = Pick<T, OptionalKeys<T>>; type UserOptional = OptionalProps<User>; // { email?: string; age?: number; } // 扁平化嵌套对象(只支持一级) type Flatten<T> = { [K in keyof T]: T[K]; }; </code> ==== 14.8.2 递归类型操作 ==== <code typescript> // 路径字符串生成 type Path<T, K extends keyof T = keyof T> = K extends string ? T[K] extends Record<string, any> ? `${K}.${Path<T[K]>}` | K : K : never; interface Data { user: { name: string; address: { city: string; country: string; }; }; settings: { theme: string; }; } type DataPaths = Path<Data>; // "user" | "user.name" | "user.address" | "user.address.city" | ... </code> ===== 14.9 本章小结 ===== 本章我们学习了: 1. **keyof** - 获取类型的所有键 2. **typeof** - 获取值的类型 3. **in** - 遍历联合类型,创建映射类型 4. **is** - 自定义类型保护函数 5. **条件类型** - 根据类型关系选择类型 6. **infer** - 在条件类型中推断类型 7. **映射类型** - 转换现有类型的属性 8. **模板字面量类型** - 字符串类型的模式操作 ===== 14.10 练习题 ===== ==== 练习 1:keyof 应用 ==== 实现一个函数,能够安全地获取对象的多个属性: <code typescript> function pluck<T, K extends keyof T>(obj: T, keys: K[]): Pick<T, K> { // 实现 } 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; } </code> 参考答案 <code typescript> function pluck<T, K extends keyof T>(obj: T, keys: K[]): Pick<T, K> { const result = {} as Pick<T, K>; keys.forEach(key => { result[key] = obj[key]; }); return result; } // 或者使用 reduce function pluck2<T, K extends keyof T>(obj: T, keys: K[]): Pick<T, K> { return keys.reduce((acc, key) => { acc[key] = obj[key]; return acc; }, {} as Pick<T, K>); } // 使用 const summary = pluck(product, ["id", "name"]); console.log(summary); // { id: 1, name: "Laptop" } </code> ==== 练习 2:类型保护实现 ==== 实现以下类型和类型保护函数: <code typescript> 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; // 实现类型保护函数和计算面积函数 </code> 参考答案 <code typescript> // 类型保护函数 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)); }); </code> ==== 练习 3:条件类型与 infer ==== 实现以下类型工具: <code typescript> // 1. 提取数组的嵌套层级深度 type ArrayDepth<T> = any; // 2. 提取函数的第 N 个参数类型 type NthParameter<T, N extends number> = any; // 3. 将联合类型转换为交叉类型 type UnionToIntersection<T> = any; </code> 参考答案 <code typescript> // 1. 数组嵌套深度 type ArrayDepth<T, Depth extends number[] = []> = T extends (infer E)[] ? ArrayDepth<E, [...Depth, 1]> : Depth["length"]; type D1 = ArrayDepth<number>; // 0 type D2 = ArrayDepth<number[]>; // 1 type D3 = ArrayDepth<number[][]>; // 2 // 2. 第 N 个参数 type NthParameter<T, N extends number> = T extends (...args: infer P) => any ? P[N] : never; function example(a: string, b: number, c: boolean) {} type P1 = NthParameter<typeof example, 0>; // string type P2 = NthParameter<typeof example, 1>; // number // 3. 联合转交叉 type UnionToIntersection<T> = (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 } </code> ==== 练习 4:映射类型实战 ==== 实现一个类型安全的表单验证系统: <code typescript> // 要求: // 1. 根据数据模型自动生成验证规则类型 // 2. 支持必填、最小长度、正则等验证规则 // 3. 验证函数返回详细的错误信息 </code> 参考答案 <code typescript> // 验证规则类型 interface ValidationRule<T> { required?: boolean; minLength?: number; maxLength?: number; pattern?: RegExp; validator?: (value: T) => string | undefined; } // 字段验证器 type FieldValidators<T> = { [K in keyof T]?: ValidationRule<T[K]>; }; // 验证错误 type ValidationErrors<T> = { [K in keyof T]?: string; }; // 验证器类 class Validator<T extends Record<string, any>> { constructor(private rules: FieldValidators<T>) {} validate(data: T): { valid: boolean; errors: ValidationErrors<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 === "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<UserForm>({ 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 </code> ===== 扩展阅读 ===== - [[TypeScript:第十五章_工具类型|下一章:工具类型]] - [[https://www.typescriptlang.org/docs/handbook/2/keyof-types.html|TypeScript 官方文档 - keyof 类型操作符]] - [[https://www.typescriptlang.org/docs/handbook/2/conditional-types.html|TypeScript 官方文档 - 条件类型]] 登录 Detach Close 该主题尚不存在 您访问的页面并不存在。如果允许,您可以使用创建该页面按钮来创建它。 typescript/第十四章_类型操作符.txt 最后更改: 2026/03/09 15:26由 张叶安 登录