在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
介绍这里引入官网一段介绍,了解个大概:
认识泛型的作用很多时候我们无法准确定义一个类型,它可以是多种类型,这种情况下我们习惯用 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' 想要获取对应属性时就会报错。
|
请发表评论