Typescript泛型帮助类型
Jacshuo Lv3

Typescript泛型帮助类型

关键字

extends

  • 可以用来集成一个class,interface, 还可以用来判断有条件类型(很多时候在ts看到extends,并不是继承)
  • 示例:
    1
    T extends U ? X : Y;
  • 上面的类型意思是,若T能够赋值给U,那么类型是X,否则为Y。原理是令T’U’分别为TU实例,并将所有类型参数替换为any,如果T’能赋值给U’,则将有条件的类型解析成X,否则为Y。上面的官方解释有点绕,下面举个例子。
    1
    2
    3
    4
    5
    6
    7
    type Words = 'a'|'b'|"c";

    type W<T> = T extends Words ? true : false;

    type WA = W<'a'>; // -> true

    type WD = W<'d'>; // -> false
    a可以复制给Words类型,所以WA为true,而d不能赋值给Words类型,所以WD为false。

    infer

  • 表示在extends条件语句中待推断的类型变量(可结合后面的returnType)。
    1
    type Union<T> = T extends Array<infer U> ? U: never
  • 如果泛型参数T满足约束条件Array那么就返回这个类型变量U.
  • 举例如下:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    type ParamType<T> = T extends (param: infer P) => any ? P: T;
    // 解析如果T能赋值给(param: infer P) => any 类型,就返回P,否则就返回T

    interface IDog {
    name: string;
    age:number;
    }

    type Func = (dog:IDog) => void;

    type Param = ParamType<Func>; // IDog
    type TypeString = ParamType<string> // string

    keyof

  • keyof可以用来取得一个对象接口的所有key值。
  • 举例:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    interface IDog {
    name: string;
    age: number;
    sex?: string;
    }

    type K1 = keyof Person; // "name" | "age" | "sex"
    type K2 = keyof Person[]; // "length" | "push" | "pop" ...
    type K3 = keyof { [x: string]: Person }; // string | number

    typeof

  • 在JavaScript中,typeof可以推断数据类型,在TypeScript中,它还有一个作用,就是获取一个变量的声明类型,如果不存在,则获取该类型的推论类型
  • 示例:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    interface IPerson {
    name: string;
    age: number;
    sex?: string;
    }

    const jack: IPerson = { name: 'jack', age: 100 };
    type Jack = typeof jack; // -> IPerson

    function foo(x: number): Array<number> {
    return [x];
    }

    type F = typeof foo; // -> (x: number) => number[]
    - Jack 这个类型别名实际上就是 jack 的类型 IPerson,而 F 的类型就是 TS 自己推导出来的 foo 的类型 (x: number) => number[]。

TypeScript内置帮助类型

Partial

1
2
3
4
5
6
7
/**
* Make all properties in T optional
* 让T中的所有属性都是可选的
*/
type Partial<T> = {
[P in keyof T]?: T[P];
};
  • 在某些情况下,我们希望类型中的所有属性都不是必需的,只有在某些条件下才存在,我们就可以使用Partial来将已声明的类型中的所有属性标识为可选的。
  • 示例:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    interface Dog {
    age: number;
    name: string;
    price: number;
    }

    type PartialDog = Partial<Dog>;
    // 等价于
    type PartialDog = {
    age?: number;
    name?: string;
    price?: number;
    }

    let dog: PartialDog = {
    age: 2,
    name: 'xiaobai'
    };
  • 在上述示例中由于我们使用Partial将所有属性标识为可选的,因此最终dog对象中虽然只包含age和name属性,但是编译器依旧没有报错,当我们不能明确地确定对象中包含哪些属性时,我们就可以通过Partial来声明。

    Required

    1
    2
    3
    4
    5
    6
    7
    /**
    * Make all properties in T required
    * 使T中的所有属性都是必需的
    */
    type Required<T> = {
    [P in keyof T]-?: T[P];
    };
  • Required的作用刚好跟Partial相反,Partial是将所有属性改成可选项,Required则是将所有类型改成必选项:其中-?是代表移除 ? 这个modifier的标识。与之对应的还有个 +? , 这个含义自然与-?之前相反, 它是用来把属性变成可选项的,+可省略,见Partial

    Readonly

    1
    2
    3
    4
    5
    6
    7
    /**
    * Make all properties in T readonly
    * 将所有属性设置为只读
    */
    type Readonly<T> = {
    readonly [P in keyof T]: T[P];
    };
  • 给子属性添加readonly的标识,如果将上面的readonly改成-readonly,就是移除子属性的readonly标识。
  • 示例:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    interface IDog{
    name: string;
    age: number;
    }
    type TDog = Readonly<IDog>;
    class TestDog {
    run() {
    let dog: IDog = {
    name: 'dd',
    age: 1
    };
    person.name = 'cc';
    let dog1: TDog = {
    name: 'read',
    age: 1
    };
    // person2.age = 3; 报错,不能赋值
    }
    }

    Pick

    1
    2
    3
    4
    5
    6
    7
    /**
    * From T, pick a set of properties whose keys are in the union K
    * 从T中,选择一组键在并集K中的属性
    */
    type Pick<T, K extends keyof T> = {
    [P in K]: T[P];
    };
  • 从源码可以开到类型K必须是类型T的key,然后用in进行遍历,将值赋给P,最后T[P]去的相应属性的值。
  • 示例:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    interface IDog {
    name: string;
    age: number;
    height: number;
    weight: number;
    }

    type PickDog = Pick<IDog, "name" | "age" | "height">;
    // 等价于
    type PickDog = {
    name: string;
    age: number;
    height: number;
    };

    let dog: PickDog = {
    name: 'wangcai',
    age: 3,
    height: 70
    };
  • 在上述示例中,由于我们只关心IDog对象中的name, ageheight是否存在,因此我们就可以使用PickIdog接口中拣选出我们关心的属性而忽略其他属性的编译检查。

    Record

    1
    2
    3
    4
    5
    6
    7
    8
    /**
    * Construct a type with a set of properties K of type T
    * 构造一个具有一组属性K(类型T)的类型
    */

    type Record<K extends keyof any, T> = {
    [P in K]: T;
    };
  • 可根据K中的所有可能值来设置key,以及value的类型。
  • 示例:
    1
    let dog = Record<string, string | number | undefined>; // -> string | number | undefined
  • 可以根据K中所有的属性的值转化为T类型,并将返回的新类型返回给dogK可以是联合类型、对象、枚举……
  • 示例:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    type petsGroup = 'dog' | 'cat';
    interface IPetInfo {
    name:string,
    age:number,
    }

    type IPets = Record<petsGroup, IPetInfo>;

    const animalsInfo:IPets = {
    dog:{
    name:'wangcai',
    age:2
    },
    cat:{
    name:'xiaobai',
    age:3
    },
    }

Exclude

1
2
3
4
5
/**
* Exclude from T those types that are assignable to U
* 从T中排除那些可分配给U的类型
*/
type Exclude<T, U> = T extends U ? never : T;
  • Pick相反,Pick用于拣选出我们需要关心的属性,而Exclude用于排除掉我们不需要关心的属性。
  • 示例:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    interface IDog {
    name: string;
    age: number;
    height: number;
    weight: number;
    sex: string;
    }

    type keys = keyof IDog; // -> "name" | "age" | "height" | "weight" | "sex"

    type ExcludeDog = Exclude<keys, "name" | "age">;
    // 等价于
    type ExcludeDog = "height" | "weight" | "sex";
  • 在上述示例中我们通过在ExcludeDog中传入我们只关心的heightweightsex属性,Exclude会帮助我们将不需要的属性进行删除。留下的属性idnamegender即为我们需要关心的属性。
  • 示例:
    1
    type T = Exclude<1 | 2, 1 | 3> // -> 2

    Extract

    1
    2
    3
    4
    5
    /**
    * Extract from T those types that are assignable to U
    * 从T中提取可分配给U的类型
    */
    type Extract<T, U> = T extends U ? T : never;
  • Extract的作用是提取出T包含在U中的元素,换种更贴近语义的说法就是从T中提取出U
  • 以上语句的意思就是:如果T能赋值给U类型的话,那么就返回T类型,否则返回never,最终结果是将TU中共有的属性提取出来。
  • 示例:
    1
    type test = Extract<'a' | 'b' | 'c' | 'd', 'a' | 'c' | 'f'|'g'>;  // -> 'a' | 'c'
  • 可以看到T'a'|'b'|'c'|'d',U'a'|'c'|'f'|'g',返回的新类型就可以将TU中的共有属性提取出来,也就是'a'|'c'了。

    Omit

    1
    2
    3
    4
    5
    /**
    * Construct a type with the properties of T except for those in type K.
    * 构造一个除类型K之外的T属性的类型
    */
    type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;
  • 在上一个用法中,我们使用Exclude来排除掉其他不需要的属性,但是在上述示例中的写法耦合度较高,当有其他类型也需要这样处理时,就必须再实现一遍相同的逻辑,使用Omit可以避免这些问题,老版本TypeScript未内置,TypeScript 3.5已经内置。
  • 示例:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    interface IDog {
    name: string;
    age: number;
    height: number;
    weight: number;
    sex: string;
    }

    // 表示忽略掉User接口中的name和age属性
    type OmitDog = Omit<IDog, "name" | "age">;
    // 等价于
    type OmitDog = {
    height: number;
    weight: number;
    sex: string;
    };

    let dog: OmitDog = {
    height: 1,
    weight: 'wangcai',
    sex: 'boy'
    };
  • 在上述示例中,我们需要忽略掉 IDog 接口中的nameage属性,则只需要将接口名和属性传入Omit即可,对于其他类型也是如此,大大提高了类型的可扩展能力,方便复用。

    NonNullable

    1
    2
    3
    4
    5
    /**
    * Exclude null and undefined from T
    * 从T中排除null和undefined
    */
    type NonNullable<T> = T extends null | undefined ? never : T;
  • 这个类型可以用来过滤类型中的 null 及 undefined 类型。
  • 示例:
    1
    2
    type test = string | number | null;
    type test1 = NonNullable<test>; // -> string | number;

    Parameters

    1
    2
    3
    4
    5
    /**
    * Obtain the parameters of a function type in a tuple
    * 在元组中获取构造函数类型的参数
    */
    type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never;
  • 该类型可以获得函数的参数类型组成的元组类型。
  • 示例:
    1
    2
    3
    4
    5
    function foo(x: number): Array<number> {
    return [x];
    }

    type P = Parameters<typeof foo>; // -> [number]
  • 此时P的真实类型就是foo的参数组成的元组类型\[number\]

    ConstructorParameters

    1
    2
    3
    4
    5
    /**
    * Obtain the parameters of a constructor function type in a tuple
    * 在元组中获取构造函数类型的参数
    */
    type ConstructorParameters<T extends new (...args: any) => any> = T extends new (...args: infer P) => any ? P : never;
  • 该类型的作用是获得类的参数类型组成的元组类型。
  • 示例:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    class Person {
    private firstName: string;
    private lastName: string;

    constructor(firstName: string, lastName: string) {
    this.firstName = firstName;
    this.lastName = lastName;
    }
    }

    type P = ConstructorParameters<typeof Person>; // -> [string, string]
  • 此时 P 就是 Person 中 constructor 的参数 firstName 和 lastName 的类型所组成的元组类型\[string,string\]

    ReturnType

    1
    2
    3
    4
    5
    /**
    * Obtain the return type of a function type
    * 获取函数类型的返回类型
    */
    type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any;
  • 该类型的作用是获取函数的返回类型。
  • 其实这里的infer R就是声明一个变量来承载传入函数签名的返回值类型, 简单说就是用它取到函数返回值的类型方便之后使用
  • 实际使用的话,就可以通过ReturnType拿到函数的返回类型
  • 示例:
    1
    2
    3
    4
    5
    function foo(x: number): Array<number> {
    return [x];
    }

    type fn = ReturnType<typeof foo>; // -> number[]

    InstanceType

    1
    2
    3
    4
    5
    6
    /**
    * Obtain the return type of a constructor function type
    * 获取构造函数类型的返回类型
    */

    type InstanceType<T extends new (...args: any) => any> = T extends new (...args: any) => infer R ? R : any;
  • 该类型的作用是获取构造函数类型的实例类型
    1
    2
    3
    4
    5
    6
    7
    class ConstructorType {
    x = 0;
    y = 0;
    }
    type test1 = InstanceType<typeof ConstructorType>; // ConstructorType

    type test1 = InstanceType<any>; // any

    ThisType

    1
    2
    3
    4
    5
    /**
    * Marker for contextual 'this' type
    * 上下文“this”类型的标记
    */
    interface ThisType<T> { }
  • 这个类型是用于指定上下文对象类型的。这类型怎么用呢,举个例子:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    interface Cat {
    name: string;
    age: number;
    }
    const obj: ThisType<Person> = {
    mimi() {
    this.name // string
    }
    }
    这样的话,就可以指定obj里的所有方法里的上下文对象改成Person这个类型了。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    // 没有ThisType情况下
    const dog = {
    wang() {
    console.log(this.age); // error,在dog中只有wang一个函数,不存在a
    }
    }
    // 使用ThisType
    const dog: { wang: any } & ThisType<{ age: number }> = {
    wang() {
    console.log(this.wang) // error,因为没有在ThisType中定义
    console.log(this.age); // ok
    }
    }
    dog.wang // ok 正常调用
    dog.age // error,在外面的话,就跟ThisType没有关系了,这里就是没有定义age了
  • 从上面的代码中可以看到,ThisType的作用是:提示其下所定义的函数,在函数body中,其调用者的类型是什么。
  • 本文标题:Typescript泛型帮助类型
  • 本文作者:Jacshuo
  • 创建时间:2021-09-20 10:15:25
  • 本文链接:https://blog.imade.life/2021/09/20/Typescript泛型帮助类型/
  • 版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!