• typescript中使用泛型


    介绍

    这里引入官网一段介绍,了解个大概:

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

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

    认识泛型的作用

    很多时候我们无法准确定义一个类型,它可以是多种类型,这种情况下我们习惯用 any 来指定它的类型,代表它可以是任意类型。any 虽好用,但是它并不是那么安全的,这时候应该更多考虑泛型。

    为了理解泛型的作用,举个例子说明。我们来创建下面这样的一个函数,传入什么参数就返回什么参数,这个函数可以看成是一个 echo 命令:

    function echoValue(arg: any): any {
      return arg
    }

    为了不限制传入的参数类型,所以使用 any 类型。此函数咋一看是没问题的,但是缺丢失了一些信息,即传入的类型与返回的类型应该是相同的,使用 any 不能保证这一点。使用 any 不是一个安全的方案,比如我们来改变一下这个函数,返回传入值的 length :

    function echoValue(arg: any): any {
      return arg.length
    }

    这样写不会报任何错误,因为 arg 可以是任意值,所以不管做什么操作都是可以的。但如果函数传入的参数是 number 类型的,显然它是没有 length 属性的,那么执行时程序就会报错了。例子虽然很牵强,但也能说明问题,any 的不确定性,注定会带来各种问题,如果动不动就使用 any,那么也失去了使用 typescript 的意义。

    现在我们使用泛型的方法来改写上面例子:

    function echoValue<T>(arg: T): T {
      return arg
    }

    T 是类型变量,它是一种特殊的变量,只用于表示类型而不是值,使用 <> 定义。定义了类型变量之后,你在函数中任何需要指定类型的地方使用 T 都代表这一种类型,这样也能保证返回值的类型与传入参数的类型是相同的了。

    我们将这个版本的 echoValue 函数称作“泛型”,因为它适用于多种类型。定义了泛型函数后,有两种方法调用它,第一种明确指定 T 的类型:

    echoValue<string>('hello world')

    第二种方法就是直接调用,更普遍。利用了类型推论 -- 即编译器会根据传入的参数自动地帮助我们确定 T 的类型:

    echoValue('hello world')

    当定义泛型时,不符合的操作都会报错,比如返回传入值的 length 时:

    function echoValue<T>(arg: T): T {
      return arg.length  // error,类型“T”上不存在属性“length”
    }

    使用泛型变量

    需要认识到泛型变量 T 可以是整个类型,也可以是某个类型的一部分,比如:

    function echoValue<T>(arg: T[]): T[] {
      console.log(arg.length)
      return arg
    }

    定义泛型变量 T,函数参数是各元素为 T 类型的数组类型,返回值是各元素为 T 类型的数组元素。

    T 并不是固定的,你可以写成 A、B或者其他名字,而且可以在一个函数中定义多个泛型变量,如下面这个例子:

    function getArray<T,U>(arg1: T, arg2: U): [T,U]{
      return [arg1, arg2]
    }

    我们定义了 T 和 U 两个泛型变量,第一个参数指定 T 类型,第二个参数指定 U 类型,函数返回一个元组包含类型 T 和 U。

    泛型类型

    我们可以定义一个泛型函数类型,泛型函数的类型与非泛型函数的类型没什么不同,只是有一个类型参数在最前面。

    直接定义:

    let echoValue: <T>(arg: T) => T = function<T>(arg: T): T {
      return arg
    }

    使用类型别名定义:

    type EchoValue = <T>(arg: T) => T
    let echoValue: EchoValue = function<T>(arg: T): T {
      return arg
    }

    使用接口定义:

    interface EchoValue{
      <T>(arg: T): T
    }
    let echoValue: EchoValue = function<T>(arg: T): T {
      return arg
    }
    // 可以使用不同的泛型参数名,只要在数量上和使用方式上能对应上就可以
    let echoValue2: EchoValue = function<U>(arg: U): U {
      return arg
    }

    对于接口而言,我们可以把泛型参数当作整个接口的一个参数,这样我们就能清楚的知道使用的具体是哪个泛型类型。如下:

    // 泛型变量作为接口的变量
    interface EchoValue<T>{
      (arg: T): T
    }
    let echoValue: EchoValue<string> = function<T>(arg: T): T {
      return arg
    }
    echoValue(123) // error,类型“123”的参数不能赋给类型“string”的参数
    
    let echoValue2: EchoValue<number> = function<U>(arg: U): U {
      return arg
    }
    echoValue2(123)

    泛型类

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

    class GenericNumber<T> {
        zeroValue: T;
        add: (x: T, y: T) => T;
    }
    
    // T 为 number 类型
    let myGenericNumber = new GenericNumber<number>();
    myGenericNumber.zeroValue = 0;
    myGenericNumber.add = function(x, y) { return x + y; };
    
    // T 为 string 类型
    let stringNumeric = new GenericNumber<string>();
    stringNumeric.zeroValue = "";
    stringNumeric.add = function(x, y) { return x + y; };

     类有两部分:静态部分和实例部分, 泛型类指的是实例部分的类型,所以类的静态属性不能使用这个泛型类型。

     

    泛型约束

    我们有时在操作某值的属性时,是事先知道它具有此属性的,但是编译器不知道,就如上面有个例子,我们访问 arg.length 是行不通的:

    function echoValue<T>(arg: T): T {
      console.log(arg.length) // 类型“T”上不存在属性“length”
      return arg
    }

    现在我们可以通过泛型约束来对泛型变量进行约束,让它至少包含 length 这一属性,具体实现如下:

    // 定义接口,接口规定必须有 length 这一属性
    interface Lengthwise{
      length: number
    }
    
    // 使用接口和 extends 关键字实现约束,此时 T 类型就必须包含 length 这一属性
    function echoValue<T extends Lengthwise>(arg: T): T {
      console.log(arg.length) // 通过,因为被约束的 T 类型是包含 length 属性的
      return arg
    }

    现在这个泛型函数被定义了约束,因此它不再是适用于任意类型:

    echoValue(3) // 类型“3”的参数不能赋给类型“Lengthwise”的参数
    
    echoValue({value: 3, length:10}) // right
    
    echoValue([1, 2, 3]) // right

    泛型约束中使用类型参数

    当我们定义一个对象,想对它做一个要求,即只能访问对象上存在的属性,该怎么做?来看看这个需求的样子:

    const getProps = (obj, propName) => {
      return obj[propName]
    }
    
    const o = {a: 'aa', b: 'bb'}
    
    getProps(o, 'c') // undefined
    我们都知道当访问这个对象的’c’属性时,这个属性是没有的,但是在开发时是不会提醒报错的。在 typescript 中,我们可以实现对这个问题的检查,要使用到一个 keyof 关键字:
    const getProps = <T, K extends keyof T>(obj: T, propName: K) => {
      return obj[propName]
    }
    
    const o = {a: 'aa', b: 'bb'}
    
    getProps(o, 'c') // error,类型“"c"”的参数不能赋给类型“"a" | "b"”的参数
    这里我们使用让 K 来继承索引类型 keyof T,可以理解 keyof T 相当于一个由泛型变量 T 的属性名构成的联合类型,这里的 K 就被约束为了只能是 'a' 或 'b',所以当我们传入字符串 'c' 想要获取对应属性时就会报错。
  • 相关阅读:
    在dataGridView中实现批量删除
    VS2005制作简单的安装程序
    [WinForms]
    TreeView的联动复选框
    TreeView
    AcceptChanges()和RejectChanges()
    用C#在WINDOWS中实现新用户帐号的创建
    测试成功的窗体应用[批量新增、删除、保存]
    TreeView的递归读取
    VS2005中部署C#应用程序
  • 原文地址:https://www.cnblogs.com/wjaaron/p/12870733.html
Copyright © 2020-2023  润新知