好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

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' 想要获取对应属性时就会报错。

查看更多关于typescript中使用泛型的详细内容...

  阅读:49次