Typescript - 泛型

泛型碱鳞,顧名思義廣泛的類型信不,是一種讓組件一次支持多種類型的技術(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è)類型變量TypeKey遗淳,其中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(通過classextend語法糖)約束類構(gòu)造函數(shù)和類類型之間的關(guān)系色乾。

小結(jié)

本文介紹了泛型/泛型約束/泛型類及其簡單的使用示例,揭開typescript泛型神秘的面紗女气,讓讀者能否快速了解和使用泛型。同時(shí)本文也是typescript類型運(yùn)算系列的第一篇掉缺,后續(xù)會(huì)帶來其他類型運(yùn)算的文章,敬請期待戈擒。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末眶明,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子筐高,更是在濱河造成了極大的恐慌搜囱,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,290評(píng)論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件柑土,死亡現(xiàn)場離奇詭異蜀肘,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)稽屏,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,107評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門扮宠,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人狐榔,你說我怎么就攤上這事坛增。” “怎么了薄腻?”我有些...
    開封第一講書人閱讀 156,872評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵收捣,是天一觀的道長。 經(jīng)常有香客問我庵楷,道長罢艾,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,415評(píng)論 1 283
  • 正文 為了忘掉前任尽纽,我火速辦了婚禮昆婿,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘蜓斧。我一直安慰自己仓蛆,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,453評(píng)論 6 385
  • 文/花漫 我一把揭開白布挎春。 她就那樣靜靜地躺著看疙,像睡著了一般。 火紅的嫁衣襯著肌膚如雪直奋。 梳的紋絲不亂的頭發(fā)上能庆,一...
    開封第一講書人閱讀 49,784評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音脚线,去河邊找鬼搁胆。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的渠旁。 我是一名探鬼主播攀例,決...
    沈念sama閱讀 38,927評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼顾腊!你這毒婦竟也來了粤铭?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,691評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤杂靶,失蹤者是張志新(化名)和其女友劉穎梆惯,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體吗垮,經(jīng)...
    沈念sama閱讀 44,137評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡垛吗,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,472評(píng)論 2 326
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了烁登。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片职烧。...
    茶點(diǎn)故事閱讀 38,622評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖防泵,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情蝗敢,我是刑警寧澤捷泞,帶...
    沈念sama閱讀 34,289評(píng)論 4 329
  • 正文 年R本政府宣布,位于F島的核電站寿谴,受9級(jí)特大地震影響锁右,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜讶泰,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,887評(píng)論 3 312
  • 文/蒙蒙 一咏瑟、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧痪署,春花似錦码泞、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,741評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至悯森,卻和暖如春宋舷,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背瓢姻。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評(píng)論 1 265
  • 我被黑心中介騙來泰國打工祝蝠, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,316評(píng)論 2 360
  • 正文 我出身青樓绎狭,卻偏偏與公主長得像细溅,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子坟岔,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,490評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容