类型演算:根据已知的信息,计算出新的类型

三个关键字

typeof

  1. typeof 作用于 数据 的时候,获取某个数据的类型;

    let obj = { name: '张三', age: 10 };
    
    type P4 = typeof obj;
    // type P4 = {
    //     name: string;
    //     age: number;
    // }
    
  2. 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

作用于类、接口、类型别名,用于获取其他类型中的 所有成员名 组成的 联合类型

  1. 用于类

    class Demo {
        name: string;
        age: number
        constructor() { }
    
        init() { }
    }
    
    type demo = keyof Demo; // name | age | init
    
  2. 用于接口

    interface Person {
      name: string;
      age: number;
      location: string;
    }
    type K1 = keyof Person; // "name" | "age" | "location"
    
  3. 用于基本数据类型

    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. 示例 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. 示例 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

条件类型的主要特性

  1. 条件类型约束:通常条件类型的检查将为我们提供一些新信息, 就像使用类型守卫缩小范围可以提供更具体的类型一样,条件类型的 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
    
  2. 分布式条件类型(分配条件类型)

    TypeScript
    TypeScript
    // 当传入的类型参数为联合类型时,他们会被 分配类型
    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)[]
    
  3. 类型推断inferTypeScript 条件类型中的关键字,用于在条件类型中声明一个待推断的类型变量,它允许我们 “提取”“捕获” 类型信息,是构建复杂类型工具的基础;

    TS
    TS
    TS
    TS
    TS
    TS
    TS
    TS
    TS
    TS
    // 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
    
  4. 递归类型:条件类型可以用于创建递归类型定义;

    TypeScript
    TypeScript
    TypeScript
    TypeScript
    TypeScript
    TypeScript
    TypeScript
    TypeScript
    // 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;
        };
    }
    */
    

常见内置条件类型

  1. Partial<T>:将类型 T 中的成员变为可选

    // 源码
    type Partial<T> = {
        [P in keyof T]?: T[P];
    };
    
    // 测试
    interface User {
        name: string;
        age: number;
    }
    let user: Partial<User>;
    
  2. Required<T>:将类型 T 中的成员变为必填

    // 源码,-? 去掉可选
    type Required<T> = {
        [P in keyof T]-?: T[P];
    };
    
    // 测试
    interface User {
        name?: string;
        age?: number;
    }
    let user: Required<User>;
    
  3. Readonly<T>:将类型 T 中的成员变为只读

    // 源码
    type Readonly<T> = {
        readonly [P in keyof T]: T[P];
    };
    
    // 测试
    interface User {
        name: string;
        age: number;
    }
    let user: Readonly<User>;
    
  4. Exclude<T, U>:从 T 中剔除可以赋值给 U 的类型

    // 源码
    type Exclude<T, U> = T extends U ? never : T;
    
    // 测试
    type T = "男" | "女" | null | undefined;
    type newT = Exclude<T, null | undefined>; // "男" | "女"
    
  5. 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"
    
  6. NonNullable<T>:从 T 中剔除 nullundefined

    // 源码
    type NonNullable<T> = T extends null | undefined ? never : T;
    
    // 测试
    type str = string | null | undefined;
    type strNotEmpty = NonNullable<str>; // string
    
  7. ReturnType<T>:获取函数返回值类型

    // 源码
    type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any;
    
    // 测试
    type func = () => number;
    type returnType = ReturnType<func>;
    
  8. 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>;
    
  9. 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: {} },
        },
    };
    

面试题

  1. 使⽤条件类型来分别得到⼀个接⼝的键名和成员列表

    TypeScript
    TypeScript
    // 使用条件类型来分别得到⼀个接⼝的键名和成员列表,可以写成泛型⼯具 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; }
    
  2. infer 提取函数重载的返回类型只能返回最后一个返回值,请返回重载函数的所有返回值联合类型

    1. 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
      
    2. 为什么会这样?
      • 实现签名优先:TypeScript 在类型推断时主要考虑函数的实现签名;
      • 最后一个重载:当使用 infer 时,TS 会查看最后一个有效的重载签名(不是实现签名);
      • 设计限制:这是 TypeScript 团队有意为之的设计选择,因为合并所有重载可能会导致类型过于宽泛;
    3. 解决方案
      TypeScript
      TypeScript
      TypeScript
      // 方案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;
      
    4. 完整解决方案示例
      // 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
      
打赏作者
您的打赏是我前进的动力
微信
支付宝
评论

中午好👏🏻,我是 ✍🏻   疯狂 codding 中...

粽子

这有关于前端开发的技术文档和你分享。

相信你可以在这里找到对你有用的知识和教程。

了解更多

目录

  1. 1. 三个关键字
    1. 1.1. typeof
    2. 1.2. keyof
    3. 1.3. in
  2. 2. 条件类型 extends
    1. 2.1. 条件类型的主要特性
    2. 2.2. 常见内置条件类型
  3. 3. 面试题