泛型碱鳞,顧名思義廣泛的類型信不,是一種讓組件一次支持多種類型的技術(shù)方案贱迟。大型軟件系統(tǒng)的研發(fā)需要更為抽象的接口定義,以此達(dá)到更為合理的代碼復(fù)用拓颓。
Hello world
在了解泛型之前语婴,先看一個(gè)簡單的加法函數(shù)定義
function add (x: number, y: number): number {
return x + y;
}
當(dāng)開發(fā)者想更多地使用此方法向外提供能力時(shí),比如字符串的相加驶睦,此時(shí)就得對(duì)其進(jìn)行改造砰左,讓接受的參數(shù)和返回的值包含字符串類型〕『剑可以使用any
來兼容多種類型缠导,代碼如下:
function add (x: any, y: any): any {
return x + y;
}
使用any
固然可以達(dá)到所謂泛型
的效果,可以支持多種類型值的傳入和返回溉痢,但此時(shí)也會(huì)丟失對(duì)類型的校驗(yàn)?zāi)芰ζг臁1热绫锼瑐魅?個(gè)number
類型的值進(jìn)行加法運(yùn)算,從樸素的認(rèn)知出發(fā)髓削,應(yīng)該返回number
類型竹挡,但函數(shù)的這種定義形式只能返回any
的類型。
typescript提供類型變量(type variables)
可以解決上述的問題立膛。類型變量
作用于類型揪罕,而非實(shí)際的值,值的類型在使用時(shí)傳入宝泵。使用類型變量
改造前面的代碼后好啰,代碼如下:
function add<T> (x: T, y: T): T {
return x + y;
}
給add
函數(shù)添加類型變量T
,這樣就可以獲取到使用者傳入的實(shí)際類型儿奶,類型系統(tǒng)也能據(jù)此做相應(yīng)的類型校驗(yàn)框往。添加類型變量后,函數(shù)在調(diào)用時(shí)也需要傳入相應(yīng)的類型闯捎,使用示例如下:
const result = add<number>(3, 4);
類型變量的傳入使用<>
而不是()
包裹椰弊,這里將number
賦值給類型變量T
,表明傳入?yún)?shù)和返回類型都是number
隙券。
Generic Types
函數(shù)的泛型定義類似于函數(shù)的定義男应,但與其不同的是沒有函數(shù)體,包含參數(shù)列表和返回類型的說明娱仔。比如將add
賦值給另外一個(gè)定義的變量沐飘,代碼如下
const myAdd: (x: T, y: T) => T = add;
也可以使用其他類型變量名來替換T,跟定義函數(shù)時(shí)使用不同的參數(shù)名稱類似牲迫,比如將T
換成InputType
耐朴,代碼如下:
const myAdd: (x: InputType, y: InputType) => InputType = add;
函數(shù)泛型的定義還可以使用調(diào)用簽名的方式(call signature
),形式類似于對(duì)象常量(object literal
)盹憎,代碼如下:
const myAdd: { <T>(x: T, y: T): T } = add;
調(diào)用簽名可以通過interface
進(jìn)行定義筛峭,這樣在一處定義,其他使用此類型的地方都可以引用陪每,該定義如下:
interface GenericAdd {
<T>(x: T, y: T): T
}
// myAdd在使用時(shí)需要傳入相應(yīng)的類型
const myAdd: GenericAdd = add;
在有些場景下影晓,可以將類型變量T
作為整個(gè)GenericAdd
的類型變量,這樣整個(gè)interface
內(nèi)部都可以使用這個(gè)類型變量檩禾,此時(shí)可以將類型變量提升作用域挂签。代碼如下:
interface GenericAdd<T> {
(x: T, y: T): T
}
// myAdd的輸入和輸出類型均為number
const myAdd: GenericAdd<number> = add;
通過上面的修改,我們生成了一個(gè)generic interface
盼产。
Generic Classes
泛型類跟泛型接口形式類似饵婆,在類名后使用<>
包裹類型變量,并在類中使用該類型變量戏售。
這里可以直接看官方的文檔示例代碼侨核,代碼如下:
class GenericNumber<NumType> {
zeroValue: NumType;
add: (x: NumType, y: NumType) => NumType;
}
let myGenericNumber = new GenericNumber<number>();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = function (x, y) {
return x + y;
};
上述的代碼傳入number
類型草穆,當(dāng)然也可以傳入string
類型,代碼如下:
let stringNumeric = new GenericNumber<string>();
stringNumeric.zeroValue = '';
stringNumeric.add = function (x, y) {
return x + y;
};
console.log(stringNumeric.add(stringNumeric.zeroValue, "test"));
不管是number
還是string
搓译,這些都是簡單的類型悲柱,用戶同樣可以傳入更為復(fù)雜的自定義類型。
??
generic class
中的靜態(tài)屬性不能應(yīng)用類型變量些己。詳情參考這里
Generic Constraints
使用類型變量時(shí)诗祸,需要關(guān)注對(duì)類型使用的限制。以前面的add
函數(shù)為例轴总,假設(shè)在字符串相加的場景中需要判斷字符串的長度,傳入的字符串長度不能小于2
博个,否則不執(zhí)行字符串加法怀樟。代碼可能如下:
function add<T> (x: T, y: T): T {
// Property 'length' does not exist on type 'Type'.
if (x.length < 2 || y.length < 2) {
return console.log('字符串的長度不能小于2');
}
return x + y;
}
在類型校驗(yàn)階段,會(huì)拋出錯(cuò)誤Property 'length' does not exist on type 'T'.
盆佣。很明顯往堡,number
類型的值是沒有length
屬性的。
假設(shè)該方法只處理具有length
屬性類型的參數(shù)的加法共耍,那么可以做相應(yīng)的類型約束虑灰,讓T
從包含length
屬性的基礎(chǔ)類型繼承。代碼如下:
interface WithLength {
length: number;
}
function add<T extends WithLength> (x: T, y: T): T {
if (x.length < 2 || y.length < 2) {
return console.log('長度不能小于2');
}
return x + y;
}
對(duì)類型添加約束后痹兜,函數(shù)調(diào)用時(shí)類型系統(tǒng)會(huì)根據(jù)約束校驗(yàn)參數(shù)和返回值是否合法穆咐。
Using Type Parameters in Generic Constraints
受益于typescript
提供的類型運(yùn)算的能力,類型變量可以從其他類型變量生成而來字旭。同樣來看官方文檔提供的獲取對(duì)象屬性的示例代碼对湃,代碼如下:
function getProperty<Type, Key extends keyof Type>(obj: Type, key: Key) {
return obj[key];
}
let x = { a: 1, b: 2, c: 3, d: 4 };
getProperty(x, "a");
//?? m 不是 x 對(duì)象的屬性,編譯階段會(huì)報(bào)錯(cuò)
getProperty(x, "m");
函數(shù)getProperty
接收兩個(gè)類型變量Type
和Key
遗淳,其中Key
是基于對(duì)Type
的類型運(yùn)算生成的拍柒。keyof是對(duì)給定類型的運(yùn)算,如果Type
為對(duì)象類型屈暗,則其結(jié)果為一個(gè)對(duì)象key組成的union
拆讯。以上面的代碼為例:Key = 'a' | 'b' | 'c' | 'd'
。
Using Class Types in Generics
在typescript中使用泛型構(gòu)建實(shí)例工廠养叛,需要關(guān)注構(gòu)造函數(shù)的使用种呐。如下代碼:
function create<Type>(c: { new (): Type }): Type {
return new c();
}
這里需要注意定義構(gòu)造函數(shù)的簽名,構(gòu)造函數(shù)返回的實(shí)例類型需給傳入的class
類型一致一铅。
下面是一個(gè)更為復(fù)雜的示例:
class BeeKeeper {
hasMask: boolean = true;
}
class ZooKeeper {
nametag: string = "Mikle";
}
class Animal {
numLegs: number = 4;
}
class Bee extends Animal {
keeper: BeeKeeper = new BeeKeeper();
}
class Lion extends Animal {
keeper: ZooKeeper = new ZooKeeper();
}
function createInstance<A extends Animal>(c: new () => A): A {
return new c();
}
createInstance(Lion).keeper.nametag;
createInstance(Bee).keeper.hasMask;
這個(gè)示例展示了如何使用prototype
(通過class
和extend
語法糖)約束類構(gòu)造函數(shù)和類類型之間的關(guān)系色乾。
小結(jié)
本文介紹了泛型/泛型約束/泛型類
及其簡單的使用示例,揭開typescript泛型神秘的面紗女气,讓讀者能否快速了解和使用泛型。同時(shí)本文也是typescript類型運(yùn)算系列的第一篇掉缺,后續(xù)會(huì)帶來其他類型運(yùn)算的文章,敬請期待戈擒。