美文网首页
三、TypeScript 泛型

三、TypeScript 泛型

作者: 輪徊傷 | 来源:发表于2021-11-17 15:37 被阅读0次

    什么是泛型

    理解:泛型就是在编译期间不确定类型(广泛之意思),在调用时,由程序员指定的泛型具体指向什么类型。泛型在传统面向对象编程语言中是极为常见的,ts中当然也执行泛型。

    通俗理解:泛型就是解决 类 接口 方法的重用性、以及对不特定数据类型的支持

    泛型的好处

    1. 函数和类可以轻松的支持多种类型,增强程序的扩展性

    2. 不必写多条函数重载,冗长的联合类型声明,增强代码的可读性

    3. 灵活控制类型之间的约束


    泛型初识
    需求:有个函数会返回任何传入它的值。

    不用泛型的话,这个函数可能是下面这样:

    function getData(value: string): string{
      return value;//传入什么返回什么
    }
    

    但是这样,这个函数我只能传字符串类型的参数给这个函数,如果想传数字类型,布尔类型呢。这个时候可以使用 any来实现

    function getData(value: any): any{
      return value;//传入什么返回什么
    }
    

    any 的缺点:\color{red}{一个变量设置类型为any后,相当于该变量关闭了TS的类型检测,一般情况下不建议使用any}

    如果想做到传入什么类型就返回什么类型,例如传入number就返回number,这时候就可以使用泛型

    function getData<T>(value:T):T{
      return value;//传入什么返回什么
    }
    第一种是,传入所有的参数,包含类型参数:
    getData<number>(123);
    getData<string>("123");
    
    第二种方法更普遍。利用了类型推论 – 即编译器会根据传入的参数自动地帮助我们确定T的类型:
    getData(123);
    getData("123");
    

    泛型类型

    一、泛型函数

    泛型函数的类型与非泛型函数的类型没什么不同,只是有一个类型参数在最前面,像函数声明一样:

    function identity<T>(arg: T): T {
        return arg;
    }
    
    let myIdentity: <T>(arg: T) => T = identity;
    

    我们也可以使用不同的泛型参数名,只要在数量上和使用方式上能对应上就可以。

    function identity<T>(arg: T): T {
        return arg;
    }
    
    let myIdentity: <U>(arg: U) => U = identity;
    

    我们还可以使用带有调用签名的对象字面量来定义泛型函数:

    function identity<T>(arg: T): T {
        return arg;
    }
    
    对象字面量的方式来定义泛型类型;
    let myIdentity: {<T>(arg: T): T} = identity;
    
    二、泛型接口

    这引导我们去写第一个泛型接口了。 我们把上面例子里的对象字面量拿出来做为一个接口:

    interface GenericIdentityFn {
        <T>(arg: T): T;
    }
    
    function identity<T>(arg: T): T {
        return arg;
    }
    
    1、函数泛型的注解方式;
    let myIdentity: <U>(arg: U) => U = identity;
    2、对象字面量的方式来定义泛型类型;
    let myIdentity: {<T>(arg: T): T} = identity;
    3、接口泛型的注解方式;
    let myIdentity: GenericIdentityFn = identity;
    

    想把泛型参数当作整个接口的一个参数。
    这样我们就能清楚的知道使用的具体是哪个泛型类型

    interface GenericIdentityFn<T> {
        (arg: T): T;
    }
    
    function identity<T>(arg: T): T {
        return arg;
    }
    
    let myIdentity: GenericIdentityFn<number> = identity;
    

    例子:

    interface GenericIdentityFn{
      (value1:string, value2:string):string;
    }
    
    function identity(value1:string, value2:string):string{
      return value1 + value2;
    }
    
    let myIdentity: GenericIdentityFn = identity
    myIdentity("name", "张三")
    
    泛型实现上面函数
    interface GenericIdentityFn {
        <T>(arg: T): T;
    }
    
    function identity<T>(arg: T): T {
        return arg;
    }
    let myIdentity: GenericIdentityFn = identity;
    
    三、泛型类

    泛型类看上去与泛型接口差不多。 泛型类使用( <> )括起泛型类型,跟在类名后面。

    class GenericNumber<T> {
      zeroValue: T;
      add: (x: T, y: T) => T;
    }
    
    使用 number 类型
    let myGenericNumber = new GenericNumber<number>();
    myGenericNumber.zeroValue = 0;
    myGenericNumber.add = function (x, y) { return x + y; };
    console.log(myGenericNumber.add(1, 2))
    
    也可以使用字符串或其它更复杂的类型。
    let stringNumeric = new GenericNumber<string>();
    stringNumeric.zeroValue = "第一次    ";
    stringNumeric.add = function (x, y) { return x + y; };
    console.log(stringNumeric.add(stringNumeric.zeroValue, "test!!!"));
    

    示例:

    普通类

    class MinClass {
      public list: number[] = [];
      add(num: number) {
        this.list.push(num);
      }
      min(): number {
        var minNum = this.list[0];
        for (var i = 0; i < this.list.length; i++) {
          if (minNum > this.list[i]) {
            minNum = this.list[i];
          }
        }
        console.log(this.list);
        return minNum;
      }
    }
    // 调用
    var m = new MinClass();
    m.add(3);
    m.add(2);
    m.add(4);
    console.log(m.min());// 2
    

    上边的只能传入数字类型,使用泛型实现上面的 类

    class MinClass<T>{
      public list: T[] = [];
      add(num: T): void {
        this.list.push(num);
      }
      min(): T {
        var minNum = this.list[0];
        for (var i = 0; i < this.list.length; i++) {
          if (minNum > this.list[i]) {
            minNum = this.list[i];
          }
        }
        return minNum;
      }
    }
    // 调用,实例化时候要先声明参数类型<bumber
    var m = new MinClass<number>();
    m.add(2);
    m.add(4);
    m.add(3);
    console.log(m.min());// 2
    
    四、约束泛型

    作某类型的一组值,并且我们知道这组值具有什么样的属性。这个时候我们想访问arg的length属性时,例如:

    function loggingIdentity<T>(arg: T): T {
      console.log(arg.length)  // Property 'length' does not exist on type 'T'.
      return arg
    }
    

    想访问arg的length属性,但是编译器并不能证明每种类型都有length属性(比如说 number 类型、boolean 类型),所以就报错了。

    这种情况可以使用泛型约束了

    function loggingIdentity<T>(arg: T[]): T[] {
      console.log(arg.length)
      return arg
    }
    
    const arr = loggingIdentity([1, 2, 3])
    

    上面代码中约束了传入的参数需要是任意类型的数组,但 Object,String类型都是有length属性的,这时候就不满足这种场景了。这时候需要对泛型进行约束,允许这个函数传入包含length属性的变量(约束泛型):

    interface Lengthwise {
      length: number
    }
    
    // type Lengthwise = {
    //   length: number
    // }
    
    function loggingIdentity<T extends Lengthwise>(arg: T): T {
      console.log(arg.length)
      return arg
    }
    
    const len01 = loggingIdentity('abc') // 3
    const len02 = loggingIdentity({ length: 12 }) // 12
    const len03 = loggingIdentity([1, 2, 3]) // 3
    

    上面代码中,定义了一个接口 Lengthwise ,在函数 loggingIdentity 的泛型中使用 extends 关键字继承了Lengthwise 接口,这样调用函数 loggingIdentity 时传的参数必须要有 length 这个属性

    索引类型(keyof)
    官方定义:keyof该操作符可以用于获取某种类型的所有键,其返回类型是联合类型(keyof的应用多第三方库的源码中常见)
    声明一个类型参数,且它被另一个类型参数所约束。 比如,现在我们想要用属性名从对象里获取这个属性。 并且我们想要确保这个属性存在于对象obj上,因此我们需要在这两个类型之间使用约束。

    function getProperty<T, K extends keyof T>(obj: T, key: K) {
        return obj[key];
    }
    
    let x = { a: 1, b: 2, c: 3, d: 4 };
    
    getProperty(x, "a"); // okay
    getProperty(x, "m"); // error: Argument of type 'm' isn't assignable to 'a' | 'b' | 'c' | 'd'.
    

    js中的Object.keys大家肯定都用过, 获取对象的键值, ts中的keyof和他类似, 可以用来获取对象类型的键值:

    type A = keyof {a:1, b:'123'} // 'a'|'b'
    type B = keyof [1, 2] // '1'|'2'|'push'...  获取到内容的同时, 还得到了Array原型上的方法和属性(实战中暂时没遇到这种需求, 了解即可)
    可以获得键值, 也可以获取对象类型的值的类型:
    
    type C = A['a'] // 等于type C = 1;
    let c:C = 2 // 错误, 值只能是1
    

    多重约束&交叉类型
    示例1
    交叉类型是将多个类型合并为一个类型。 这让我们可以把现有的多种类型叠加到一起成为一种类型,它包含了所需的所有类型的特性。 例如,Person & Serializable & Loggable同时是PersonSerializableLoggable。 就是说这个类型的对象同时拥有了这三种类型的成员。

    我们大多是在混入(mixins)或其它不适合典型面向对象模型的地方看到交叉类型的使用。 (在JavaScript里发生这种情况的场合很多!) 下面是如何创建混入的一个简单例子:

    function extend<T, U>(first: T, second: U): T & U {
        let result = <T & U>{};
        for (let id in first) {
            (<any>result)[id] = (<any>first)[id];
        }
        for (let id in second) {
            if (!result.hasOwnProperty(id)) {
                (<any>result)[id] = (<any>second)[id];
            }
        }
        return result;
    }
    
    class Person {
        constructor(public name: string) { }
    }
    interface Loggable {
        log(): void;
    }
    class ConsoleLogger implements Loggable {
        log() {
            // ...
        }
    }
    var jim = extend(new Person("Jim"), new ConsoleLogger());
    var n = jim.name;
    jim.log();
    

    示例2

    interface Sentence {
      content: string;
      title: string;
    }
    interface Music {
      url: string;
    }
    class Test<T extends Sentence & Music>{
      props: T
      constructor(public arg: T) {
        this.props = arg
      }
      info() {
        return {
          url: this.props.url,
          content: this.props.content,
          title: this.props.title
        }
      }
    }
    
    let a = new Test({
      title: "泛型",
      content: "xasdjoasdoijasiodhoia",
      url: "aaaaaaaaaaaa"
    })
    console.log(a);
    

    在泛型里使用类类型
    在TypeScript使用泛型创建工厂函数时,需要引用构造函数的类类型。比如,

    1、参数c的注解是一个对象,可以把它看成一个接口;
    2、对象里面的属性是一个方法( new() ),方法的返回值是 (T)
            可以写成 function create<T>(c: new() => T): T { return new c(); }
    3、有一个 new 方法说明这个方法是个构造方法,那么 T 就代表构造方法的实例
    4、返回的是实例 T,所以需要调用 new方法来获取实例
    function create<T>(c: {new(): T; }): T {
      return new c();
    }
    

    不要乱用泛型

    泛型主要是为了约束, 或者说缩小类型范围, 如果不能约束功能, 就代表不需要用泛型:

    function convert<T>(input:T[]):number{
        return input.length;
    }
    

    这样用泛型就没有什么意义了, 和any类型没有什么区别.

    泛型的使用场景
    大神的泛型讲解

    相关文章

      网友评论

          本文标题:三、TypeScript 泛型

          本文链接:https://www.haomeiwen.com/subject/khobzltx.html