类型演算:根据已知的信息,计算出新的类型
三个关键字
typeof
-
typeof 作用于 数据 的时候,获取某个数据的类型;
let obj = { name: '张三', age: 10 }; type P4 = typeof obj; // type P4 = { // name: string; // age: number; // }
-
typeof 作用于 类 的时候,得到的类型是该类的构造函数;
class User { name: string; age: number; } // function createUser(cls: new () => User): User { // return new cls(); // } // 两种方式等价 function createUser(cls: typeof User): User { return new cls(); } const user = createUser(User);
keyof
作用于类、接口、类型别名,用于获取其他类型中的 所有成员名 组成的 联合类型
-
用于类
class Demo { name: string; age: number constructor() { } init() { } } type demo = keyof Demo; // name | age | init
-
用于接口
interface Person { name: string; age: number; location: string; } type K1 = keyof Person; // "name" | "age" | "location"
-
用于基本数据类型
let K1: keyof boolean; // let K1: "valueOf" let K2: keyof number; // let K2: "toString" | "toFixed" | "toExponential" | ... let K3: keyof symbol; // let K1: "valueOf" | ...
in
该关键字往往和 keyof 联用,限制某个索引类型的取值范围;
-
示例 1:
interface User { loginId: string loginpwd: string } // 将 T 的所有属性转成只读的 type Readonly<T> = { readonly [p in keyof T]: T[p] } // 将 T 的所有属性转成可选的 type Partial<T> = { [p in keyof T]?: T[p] }
-
示例 2:
interface Article { title: string publishDate: Date } //将 T 的所有属性值类型变成字符串,得到一个新类型 type String<T> = { [p in keyof T]: string } // 测试 const u: String<Article> = { title: "Sfsdf", publishDate: "sdf" }
条件类型 extends
这个语法表示:如果类型 T 可以赋值给类型 U,则结果类型为 X,否则为 Y
T extends U ? X : Y
条件类型的主要特性
-
条件类型约束
:通常条件类型的检查将为我们提供一些新信息, 就像使用类型守卫缩小范围可以提供更具体的类型一样,条件类型的 true 分支将根据我们检查的类型进一步约束泛型;// 约束 T 必须要有 message 属性 type MessageOf<T> = T extends { message: unknown } ? T["message"] : never; interface Email { message: string; } interface Dog { bark(): void; } type EmailMessageContents = MessageOf<Email>; // string type DogMessageContents = MessageOf<Dog>; // never
-
分布式条件类型(分配条件类型)
:TypeScriptTypeScript// 当传入的类型参数为联合类型时,他们会被 分配类型 type Distributed<T> = T extends any ? T[] : never; type Result = Distributed<string | number>; // string[] | number[]
// 要避免这种行为,可以用方括号括起 extends 关键字的两边 type Distributed<T> = [T] extends [any] ? T[] : never; type Result = Distributed<string | number>; // (string | number)[]
-
类型推断
:infer 是 TypeScript 条件类型中的关键字,用于在条件类型中声明一个待推断的类型变量,它允许我们 “提取” 或 “捕获” 类型信息,是构建复杂类型工具的基础;TSTSTSTSTSTSTSTSTSTS// 1. 提取函数返回类型: 检查 T 是否是函数类型,如果是则提取返回类型到 R 中,否则返回 any type ReturnType<T> = T extends (...args: any[]) => infer R ? R : any; function foo() { return 42; } type FooReturn = ReturnType<typeof foo>; // number
// 2. 提取函数参数类型:匹配函数参数部分 (...args: infer P) 将参数元组类型捕获到 P 中,返回参数元组类型,如果不是函数,返回 never type Parameters<T> = T extends (...args: infer P) => any ? P : never; function bar(x: string, y: number): void {} type BarParams = Parameters<typeof bar>; // [x: string, y: number]
// 3. 提取数组元素类型 type ElementType<T> = T extends (infer U)[] ? U : T; type NumArray = number[]; type Num = ElementType<NumArray>; // number type Str = ElementType<string>; // string (不是数组,返回原类型)
// 4. 提取 Promise 的 resolve 类型:检查 T 是否是 Promise<infer U> 类型,如果是则提取泛型参数 U,如果不是 Promise,返回原类型 type Awaited<T> = T extends Promise<infer U> ? U : T; type PromiseNum = Promise<number>; type Num = Awaited<PromiseNum>; // number type NotPromise = Awaited<string>; // string
// 5. 提取构造函数实例类型 type InstanceType<T> = T extends new (...args: any[]) => infer R ? R : any; class MyClass {} type MyInstance = InstanceType<typeof MyClass>; // MyClass
// 6. 提取构造函数参数类型 type ConstructorParameters<T> = T extends new (...args: infer P) => any ? P : never; class Person { constructor(public name: string, public age: number) {} } type PersonParams = ConstructorParameters<typeof Person>; // [name: string, age: number]
// 7. 提取对象属性类型 type PropType<T, K extends keyof T> = T extends { [P in K]: infer U } ? U : never; interface User { name: string; age: number; } type NameType = PropType<User, 'name'>; // string
// 8. 提取第一个/最后一个元素类型 type First<T extends any[]> = T extends [infer U, ...any[]] ? U : never; type Last<T extends any[]> = T extends [...any[], infer U] ? U : never; type Arr = [string, number, boolean]; type FirstElement = First<Arr>; // string type LastElement = Last<Arr>; // boolean
// 9. 提取函数 this 类型 type ThisParameter<T> = T extends (this: infer U, ...args: any[]) => any ? U : unknown; function sayHello(this: { name: string }) {} type SayHelloThis = ThisParameter<typeof sayHello>; // { name: string }
// 10. 提取类型守卫的断言类型 type GuardedType<T> = T extends (arg: any) => arg is infer U ? U : never; function isString(x: any): x is string { return typeof x === 'string'; } type Guarded = GuardedType<typeof isString>; // string
-
递归类型
:条件类型可以用于创建递归类型定义;TypeScriptTypeScriptTypeScriptTypeScriptTypeScriptTypeScriptTypeScriptTypeScript// 1. 递归提取嵌套 Promise:递归解包嵌套的 Promise 类型,每次递归检查是否是 Promise,如果是则继续解包,直到不是 Promise 类型时返回 type DeepAwaited<T> = T extends Promise<infer U> ? DeepAwaited<U> : T; type NestedPromise = Promise<Promise<Promise<number>>>; type Num = DeepAwaited<NestedPromise>; // number
// 2. 深度可选类型: type DeepPartial<T> = { [P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P]; }; interface User { name: string; address: { street: string; city: string; }; } type PartialUser = DeepPartial<User>; /* 等价于: { name?: string; address?: { street?: string; city?: string; }; } */
// 3. 递归展开嵌套数组 type Flatten<T> = T extends Array<infer U> ? Flatten<U> : T; type Nested = [1, [2, [3, [4, [5]]]]]; type Flat = Flatten<Nested>; // 1 | 2 | 3 | 4 | 5
// 4. 递归获取所有属性路径 type Paths<T, Prefix extends string = ""> = T extends object ? { [K in keyof T]: K extends string ? `${Prefix}${K}` | `${Prefix}${K}.${Paths<T[K], "">}` : never; }[keyof T] : never; type UserPaths = Paths<User>; // "name" | "address" | "address.street" | "address.city"
// 5. 递归将联合类型转为交叉类型 type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never; type Example = UnionToIntersection<{ a: number } | { b: string }>; // { a: number } & { b: string }
// 6. 递归去除 null/undefined type NonNullableDeep<T> = { [P in keyof T]: T[P] extends object nNullableDeep<NonNullable<T[P]>> : NonNullable<T[P]>; }; type MaybeUser = { name?: string | null; address?: { street?: string | null; city: string | undefined; } | null; }; type SafeUser = NonNullableDeep<MaybeUser>; /* 等价于: { name: string; address: { street: string; city: string; }; } */
// 7. 递归实现 JSON 类型 type JSONValue = string | number | boolean | null | JSONValue[] | { [key: string]: JSONValue }; type ValidateJSON<T> = T extends JSONValue ? T : never; type Valid = ValidateJSON<{ name: string; scores: number[]; meta: { active: boolean }; }>; // 通过验证 type Invalid = ValidateJSON<{ date: Date; // Error: Date 不是 JSONValue }>;
// 8. 递归实现深度合并类型(递归合并两个对象类型,对于冲突属性,以第二个类型 U 为准) type DeepMerge<T, U> = T extends object ? U extends object ? { [K in keyof T | keyof U]: K extends keyof U ? K extends keyof T ? DeepMerge<T[K], U[K]> : U[K] : K extends keyof T ? T[K] : never; } : U : U; type A = { name: string; address: { street: string; city: string; }; }; type B = { age: number; address: { zip: string; }; }; type Merged = DeepMerge<A, B>; /* 等价于: { name: string; age: number; address: { street: string; city: string; zip: string; }; } */
常见内置条件类型
-
Partial<T>
:将类型 T 中的成员变为可选// 源码 type Partial<T> = { [P in keyof T]?: T[P]; }; // 测试 interface User { name: string; age: number; } let user: Partial<User>;
-
Required<T>
:将类型 T 中的成员变为必填// 源码,-? 去掉可选 type Required<T> = { [P in keyof T]-?: T[P]; }; // 测试 interface User { name?: string; age?: number; } let user: Required<User>;
-
Readonly<T>
:将类型 T 中的成员变为只读// 源码 type Readonly<T> = { readonly [P in keyof T]: T[P]; }; // 测试 interface User { name: string; age: number; } let user: Readonly<User>;
-
Exclude<T, U>
:从 T 中剔除可以赋值给 U 的类型// 源码 type Exclude<T, U> = T extends U ? never : T; // 测试 type T = "男" | "女" | null | undefined; type newT = Exclude<T, null | undefined>; // "男" | "女"
-
Extract<T, U>
:提取 T 中可以赋值给 U 的类型// 源码 type Extract<T, U> = T extends U ? T : never; // 测试 type R5 = Extract<'a' | 'b' | 'c' | 'd', 'a' | 'b' | 'c' | 'e'>; // "a" | "b" | "c"
-
NonNullable<T>
:从 T 中剔除 null 和 undefined// 源码 type NonNullable<T> = T extends null | undefined ? never : T; // 测试 type str = string | null | undefined; type strNotEmpty = NonNullable<str>; // string
-
ReturnType<T>
:获取函数返回值类型// 源码 type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any; // 测试 type func = () => number; type returnType = ReturnType<func>;
-
InstanceType<T>
:获取构造函数类型的实例类型// 源码 type InstanceType<T extends abstract new (...args: any) => any> = T extends abstract new (...args: any) => infer R ? R : any; // 测试 class User { } let user: InstanceType<typeof User>;
-
Record
:用于创建一个对象类型,其键和值的类型可以分别指定type Tree<T> = { value: T; children: Record<string, Tree<T>>; }; // 测试(递归) const tree: Tree<number> = { value: 1, children: { a: { value: 2, children: {} }, b: { value: 3, children: {} }, }, };
面试题
-
使⽤条件类型来分别得到⼀个接⼝的键名和成员列表
TypeScriptTypeScript// 使用条件类型来分别得到⼀个接⼝的键名和成员列表,可以写成泛型⼯具 Signs<T> 和 Keys<T> type T = { [k: string | symbol | number]: string | number; a: string; b: number; c: 1; }; type keys = ... type signs = ...
type T = { [k: string | symbol | number]: string | number; a: string; b: number; c: 1; }; // 提取键名 type Keys<T> = keyof T; // keyof T 会提取接口 T 的所有键名,并将其作为联合类型返回 // 由于 [k: string | symbol | number] 是索引签名,它不会影响显式定义的键名(如 a、b、c) type keys = Keys<T>; // "a" | "b" | "c" // 提取成员类型 type Signs<T> = { [K in keyof T]: T[K]; // [K in keyof T] 会遍历接口 T 的所有键名,T[K] 会提取对应键名的类型 }; type signs = Signs<T>; // { a: string; b: number; c: 1; }
-
infer 提取函数重载的返回类型只能返回最后一个返回值,请返回重载函数的所有返回值联合类型
- TypeScript 在处理函数重载时使用 infer 提取返回类型确实有一个重要的限制:它只会推断最后一个重载签名的返回类型,而不是所有可能的重载返回类型的联合
// 函数重载声明 function overloaded(a: number): number; function overloaded(a: string): string; // 实现签名(不被 infer 捕获) function overloaded(a: any): any { return a; } // 尝试提取返回类型 type ExtractedReturnType<T> = T extends (...args: any[]) => infer R ? R : never; type Test = ExtractedReturnType<typeof overloaded>; // string,而不是 number | string
- 为什么会这样?
- 实现签名优先:TypeScript 在类型推断时主要考虑函数的实现签名;
- 最后一个重载:当使用 infer 时,TS 会查看最后一个有效的重载签名(不是实现签名);
- 设计限制:这是 TypeScript 团队有意为之的设计选择,因为合并所有重载可能会导致类型过于宽泛;
- 解决方案
TypeScriptTypeScriptTypeScript
// 方案1:手动合并重载类型(推荐使用) type Overloads = { (a: number): number; (a: string): string; }; type GetAllReturnTypes<T> = T extends { (...args: any[]): infer R } ? R : T extends (...args: any[]) => infer R ? R : never; type CombinedReturns = GetAllReturnTypes<Overloads>; // number | string
// 方案2:使用类型查询 + 联合类型(维护成本更高) type ReturnTypes<T> = T extends { (...args: any[]): infer R1; (...args: any[]): infer R2 } ? R1 | R2 : T extends (...args: any[]) => infer R ? R : never; type Test = ReturnTypes<typeof overloaded>; // number | string
// 方案3:针对特定数量的重载定制(最差) type OverloadReturnType<T> = T extends { (...args: any[]): infer R1; (...args: any[]): infer R2; (...args: any[]): infer R3; } ? R1 | R2 | R3 : T extends (...args: any[]) => infer R ? R : never;
- 完整解决方案示例
// 1. 定义所有重载为接口 interface Overloaded { (a: number): number; (a: string): string; (a: boolean): boolean; } // 2. 创建提取工具类型 type UnionReturnType<T> = T extends { (...args: any[]): infer R } ? R : never; // 3. 获取联合返回类型 type Returns = UnionReturnType<Overloaded>; // number | string | boolean // 4. 应用到实现函数 const impl: Overloaded = (a: any) => a; type ImplReturns = UnionReturnType<typeof impl>; // number | string | boolean
- TypeScript 在处理函数重载时使用 infer 提取返回类型确实有一个重要的限制:它只会推断最后一个重载签名的返回类型,而不是所有可能的重载返回类型的联合
TypeScript👉 泛型初探
上一篇