一、范型是什么
软件工程中,我们不仅要创建一致的定义良好的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;
}
网友评论