一、范型是什么
軟件工程中,我們不僅要創(chuàng)建一致的定義良好的API,同時也要考慮可重用性。 組件不僅能夠支持當前的數(shù)據(jù)類型,同時也能支持未來的數(shù)據(jù)類型,這在創(chuàng)建大型系統(tǒng)時為你提供了十分靈活的功能。
在像C#和Java這樣的語言中,可以使用泛型來創(chuàng)建可重用的組件,一個組件可以支持多種類型的數(shù)據(jù)。 這樣用戶就可以以自己的數(shù)據(jù)類型來使用組件。
設(shè)計泛型的關(guān)鍵目的是在成員之間提供有意義的約束,這些成員可以是:類的實例成員、類的方法、函數(shù)參數(shù)和函數(shù)返回值。
//js
function identity(val){
return val
}
console.log(identity(1)) // 1
//ts 我們將 Number 類型分配給參數(shù)和返回類型
function identity(val:Number):Number{
return val
}
console.log(identity(1)) // 1
這里 identity 的問題是我們將 Number 類型分配給參數(shù)和返回類型,使該函數(shù)僅可用于該原始類型。但該函數(shù)并不是可擴展或通用的,很明顯這并不是我們所希望的。
我們確實可以把 Number 換成 any,我們失去了定義應該返回哪種類型的能力,并且在這個過程中使編譯器失去了類型保護的作用。我們的目標是讓 identity 函數(shù)可以適用于任何特定的類型,為了實現(xiàn)這個目標,我們可以使用泛型來解決這個問題,具體實現(xiàn)方式如下:
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”的參數(shù)不能賦給類型“Number”的參數(shù)。ts(2345)
其實并不是只能定義一個類型變量,我們可以引入希望定義的任何數(shù)量的類型變量。比如我們引入一個新的類型變量 U,用于擴展我們定義的 identity 函數(shù):
function identity<T,U>(val:T,msg:U):T{
console.log(msg)
return val
}
identity<Number,string>(1,'this is a num')
除了為類型變量顯式設(shè)定值之外,一種更常見的做法是使編譯器自動選擇這些類型,從而使代碼更簡潔。我們可以完全省略尖括號,比如:
function identity<T,U>(val:T,msg:U):T{
console.log(msg)
return val
}
identity(1,'this is a num')
相比之前定義的 identity 函數(shù),新的 identity 函數(shù)增加了一個類型變量 U,但該函數(shù)的返回類型我們?nèi)匀皇褂?T。如果我們想要返回兩種類型的對象該怎么辦呢?針對這個問題,我們有多種方案,其中一種就是使用元組,即為元組設(shè)置通用的類型:
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' ]
雖然使用元組解決了上述的問題,但有沒有其它更好的方案呢?答案是有的,你可以使用泛型接口。
二、范型接口
為了解決上面提到的問題,首先讓我們創(chuàng)建一個用于的 identity 函數(shù)通用 Identities 接口:
interface Identities<V, M> {
value: V,
message: M
}
在上述的 Identities 接口中,我們引入了類型變量 V 和 M,來進一步說明有效的字母都可以用于表示類型變量,
之后我們就可以將 Identities 接口作為 identity 函數(shù)的返回類型:
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'))
泛型除了可以應用在函數(shù)和接口之外,它也可以應用在類中,下面我們就來看一下在類中如何使用泛型。
三、范型類
暫時不看xx
相信看到這里一些讀者會有疑問,我們在什么時候需要使用泛型呢?通常在決定是否使用泛型時,我們有以下兩個參考標準:
當你的函數(shù)、接口或類將處理多種數(shù)據(jù)類型時;
當函數(shù)、接口或類在多個地方使用該數(shù)據(jù)類型時。
四、范型約束
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 用于告訴編譯器,我們支持已經(jīng)實現(xiàn) Length 接口的任何類型。
之后,當我們使用不含有 length 屬性的對象作為參數(shù)調(diào)用 identity 函數(shù)時,TypeScript 會提示相關(guān)的錯誤信息:
interface Length{
length: number;
}
function identity<T extends Length>(arg: T): T {
console.log(arg.length); // Error
return arg;
}
identity(3) //報錯:類型“number”的參數(shù)不能賦給類型“Length”的參數(shù)。ts(2345)
identity({length: 3})
identity([1, 2, 3])

此外,我們還可以使用 , 號來分隔多種約束類型,比如:<T extends Length, Type2, Type3>。而對于上述的 length 屬性問題來說,如果我們顯式地將變量設(shè)置為數(shù)組類型,也可以解決該問題,具體方式如下:
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;
}