ts 范型

作者: vivienYang2019 | 来源:发表于2023-07-02 13:34 被阅读0次

    一、范型是什么

    软件工程中,我们不仅要创建一致的定义良好的API,同时也要考虑可重用性。 组件不仅能够支持当前的数据类型,同时也能支持未来的数据类型,这在创建大型系统时为你提供了十分灵活的功能。

    在像C#和Java这样的语言中,可以使用泛型来创建可重用的组件,一个组件可以支持多种类型的数据。 这样用户就可以以自己的数据类型来使用组件。

    设计泛型的关键目的是在成员之间提供有意义的约束,这些成员可以是:类的实例成员、类的方法、函数参数和函数返回值。

    //js
    function identity(val){
      return val
    }
    console.log(identity(1)) // 1
    
    //ts 我们将 Number 类型分配给参数和返回类型
    function identity(val:Number):Number{
      return val
    }
    console.log(identity(1)) // 1
    

    这里 identity 的问题是我们将 Number 类型分配给参数和返回类型,使该函数仅可用于该原始类型。但该函数并不是可扩展或通用的,很明显这并不是我们所希望的。
    我们确实可以把 Number 换成 any,我们失去了定义应该返回哪种类型的能力,并且在这个过程中使编译器失去了类型保护的作用。我们的目标是让 identity 函数可以适用于任何特定的类型,为了实现这个目标,我们可以使用泛型来解决这个问题,具体实现方式如下:

    function identity<T>(val:T):T{
      return val
    }
    console.log(identity<Number>(1)) // 1
    console.log(identity<string>('aaa')) // 'aaa'
    console.log(identity<string>('aaa')) // Error:类型“string”的参数不能赋给类型“Number”的参数。ts(2345)
    

    其实并不是只能定义一个类型变量,我们可以引入希望定义的任何数量的类型变量。比如我们引入一个新的类型变量 U,用于扩展我们定义的 identity 函数:

    function identity<T,U>(val:T,msg:U):T{
        console.log(msg)
        return val
    }
    identity<Number,string>(1,'this is a num')
    

    除了为类型变量显式设定值之外,一种更常见的做法是使编译器自动选择这些类型,从而使代码更简洁。我们可以完全省略尖括号,比如:

    function identity<T,U>(val:T,msg:U):T{
        console.log(msg)
        return val
    }
    identity(1,'this is a num')
    

    相比之前定义的 identity 函数,新的 identity 函数增加了一个类型变量 U,但该函数的返回类型我们仍然使用 T。如果我们想要返回两种类型的对象该怎么办呢?针对这个问题,我们有多种方案,其中一种就是使用元组,即为元组设置通用的类型:

    function identity<T,U>(val:T,msg:U):[T,U]{
      console.log(msg)
      return [val,msg]
    }
    let a=identity(1,'this is a num')
    console.log(a)
    // this is a num
    // [ 1, 'this is a num' ]
    

    虽然使用元组解决了上述的问题,但有没有其它更好的方案呢?答案是有的,你可以使用泛型接口。

    二、范型接口

    为了解决上面提到的问题,首先让我们创建一个用于的 identity 函数通用 Identities 接口:

    interface Identities<V, M> {
      value: V,
      message: M
    }
    

    在上述的 Identities 接口中,我们引入了类型变量 V 和 M,来进一步说明有效的字母都可以用于表示类型变量,
    之后我们就可以将 Identities 接口作为 identity 函数的返回类型:

    interface Identities<V, M> {
      val: V,
      msg: M
    }
    function identity<T,U>(val:T,msg:U):Identities<T,U>{
      console.log(val,typeof val)
      console.log(msg,typeof msg)
      let _identity:Identities<T,U> = {
        val: val,
        msg: msg
      }
      return _identity
    }
    console.log(identity(1,'hello'))
    

    泛型除了可以应用在函数和接口之外,它也可以应用在类中,下面我们就来看一下在类中如何使用泛型。

    三、范型类

    暂时不看xx

    相信看到这里一些读者会有疑问,我们在什么时候需要使用泛型呢?通常在决定是否使用泛型时,我们有以下两个参考标准:
    当你的函数、接口或类将处理多种数据类型时;
    当函数、接口或类在多个地方使用该数据类型时。

    四、范型约束

    4.1 确保属性存在
    function identity<T>(arg: T): T {
      console.log(arg.length); // Error
      return arg;
    }
    

    在这种情况下,编译器将不会知道 T 是否含有 length 属性,
    尤其是在可以将任何类型赋给类型变量 T 的情况下。我们需要做的就是让类型变量 extends 一个含有我们所需属性的接口,比如这样:

    interface Length {
      length: number;
    }
    
    function identity<T extends Length>(arg: T): T {
      console.log(arg.length); // 可以获取length属性
      return arg;
    }
    
    

    看到这里
    T extends Length 用于告诉编译器,我们支持已经实现 Length 接口的任何类型。
    之后,当我们使用不含有 length 属性的对象作为参数调用 identity 函数时,TypeScript 会提示相关的错误信息:

    interface Length{
      length: number;
    }
    function identity<T extends Length>(arg: T): T {
      console.log(arg.length); // Error
      return arg;
    }
    identity(3) //报错:类型“number”的参数不能赋给类型“Length”的参数。ts(2345)
    identity({length: 3})
    identity([1, 2, 3])
    
    image.png

    此外,我们还可以使用 , 号来分隔多种约束类型,比如:<T extends Length, Type2, Type3>。而对于上述的 length 属性问题来说,如果我们显式地将变量设置为数组类型,也可以解决该问题,具体方式如下:

    function identity<T>(arg: T[]): T[] {
       console.log(arg.length);  
       return arg; 
    }
    
    // or
    function identity<T>(arg: Array<T>): Array<T> {      
      console.log(arg.length);
      return arg; 
    }
    
    
    4.2 检查对象上的键是否存在

    相关文章

      网友评论

          本文标题:ts 范型

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