TypeScript 泛型篇

泛型

軟件工程中黄鳍,我們不僅要?jiǎng)?chuàng)建定義良好且一致的 API边器,同時(shí)也要考慮可重用性。 組件不僅能夠支持當(dāng)前的數(shù)據(jù)類型碰逸,同時(shí)也能支持未來的數(shù)據(jù)類型,這在創(chuàng)建大型系統(tǒng)時(shí)為你提供了十分靈活的功能阔加。

在像 C# 和 Java 這樣的語言中饵史,可以使用泛型來創(chuàng)建可重用的組件,一個(gè)組件可以支持多種類型的數(shù)據(jù)。 這樣用戶就可以以自己的數(shù)據(jù)類型來使用組件胳喷。

基礎(chǔ)示例

下面來創(chuàng)建第一個(gè)使用泛型的例子:identity 函數(shù)湃番。 這個(gè)函數(shù)會(huì)返回任何傳入它的值。 你可以把這個(gè)函數(shù)當(dāng)成是 echo 命令吭露。

不用泛型的話吠撮,這個(gè)函數(shù)可能是下面這樣:

function identity(arg: number): number {
  return arg
}

或者,我們使用 any 類型來定義函數(shù):

function identity(arg: any): any {
  return arg
}

使用 any 類型會(huì)導(dǎo)致這個(gè)函數(shù)可以接收任何類型的 arg 參數(shù)讲竿,但是這樣就丟失了一些信息:傳入的類型與返回的類型應(yīng)該是相同的泥兰。如果我們傳入一個(gè)數(shù)字,我們只知道任何類型的值都有可能被返回题禀。

因此鞋诗,我們需要一種方法使返回值的類型與傳入?yún)?shù)的類型是相同的。這里迈嘹,我們使用了類型變量削彬,它是一種特殊的變量,只用于表示類型而不是值秀仲。

function identity<T>(arg: T): T {
  return arg
}

我們給 identity 添加了類型變量 T融痛。 T 幫助我們捕獲用戶傳入的類型(比如:number),之后我們就可以使用這個(gè)類型神僵。 之后我們?cè)俅问褂昧?T 當(dāng)做返回值類型⊙闼ⅲ現(xiàn)在我們可以知道參數(shù)類型與返回值類型是相同的了。這允許我們跟蹤函數(shù)里使用的類型的信息保礼。

我們把這個(gè)版本的 identity 函數(shù)叫做泛型安券,因?yàn)樗梢赃m用于多個(gè)類型。 不同于使用 any氓英,它不會(huì)丟失信息,像第一個(gè)例子那像保持準(zhǔn)確性鹦筹,傳入數(shù)值類型并返回?cái)?shù)值類型铝阐。

我們定義了泛型函數(shù)后,可以用兩種方法使用铐拐。 第一種是徘键,傳入所有的參數(shù),包含類型參數(shù):

let output = identity<string>('myString')

這里我們明確的指定了 Tstring 類型遍蟋,并做為一個(gè)參數(shù)傳給函數(shù)吹害,使用了 <> 括起來而不是 ()

第二種方法更普遍虚青。利用了類型推論 -- 即編譯器會(huì)根據(jù)傳入的參數(shù)自動(dòng)地幫助我們確定 T 的類型:

let output = identity('myString')

注意我們沒必要使用尖括號(hào)(<>)來明確地傳入類型它呀;編譯器可以查看 myString 的值,然后把 T 設(shè)置為它的類型。 類型推論幫助我們保持代碼精簡和高可讀性纵穿。如果編譯器不能夠自動(dòng)地推斷出類型的話下隧,只能像上面那樣明確的傳入 T 的類型,在一些復(fù)雜的情況下谓媒,這是可能出現(xiàn)的淆院。

使用泛型變量

使用泛型創(chuàng)建像 identity 這樣的泛型函數(shù)時(shí),編譯器要求你在函數(shù)體必須正確的使用這個(gè)通用的類型句惯。 換句話說土辩,你必須把這些參數(shù)當(dāng)做是任意或所有類型。

看下之前 identity 例子:

function identity<T>(arg: T): T {
  return arg
}

如果我們想打印出 arg 的長度抢野。 我們很可能會(huì)這樣做:

function loggingIdentity<T>(arg: T): T {
  console.log(arg.length)
  return arg
}

如果這么做拷淘,編譯器會(huì)報(bào)錯(cuò)說我們使用了 arg.length 屬性,但是沒有地方指明 arg 具有這個(gè)屬性蒙保。記住辕棚,這些類型變量代表的是任意類型,所以使用這個(gè)函數(shù)的人可能傳入的是個(gè)數(shù)字邓厕,而數(shù)字是沒有 .length 屬性的逝嚎。

現(xiàn)在假設(shè)我們想操作 T 類型的數(shù)組而不直接是 T。由于我們操作的是數(shù)組详恼,所以 .length 屬性是應(yīng)該存在的补君。我們可以像創(chuàng)建其它數(shù)組一樣創(chuàng)建這個(gè)數(shù)組:

function loggingIdentity<T>(arg: T[]): T[] {
  console.log(arg.length)
  return arg
}

你可以這樣理解 loggingIdentity 的類型:泛型函數(shù) loggingIdentity,接收類型參數(shù) T 和參數(shù) arg昧互,它是個(gè)元素類型是 T 的數(shù)組挽铁,并返回元素類型是T 的數(shù)組。 如果我們傳入數(shù)字?jǐn)?shù)組敞掘,將返回一個(gè)數(shù)字?jǐn)?shù)組叽掘,因?yàn)榇藭r(shí) T 的的類型為 number。 這可以讓我們把泛型變量 T 當(dāng)做類型的一部分使用玖雁,而不是整個(gè)類型更扁,增加了靈活性。

泛型類型

上一節(jié)赫冬,我們創(chuàng)建了 identity 通用函數(shù)浓镜,可以適用于不同的類型。 在這節(jié)劲厌,我們研究一下函數(shù)本身的類型膛薛,以及如何創(chuàng)建泛型接口。

泛型函數(shù)的類型與非泛型函數(shù)的類型沒什么不同补鼻,只是有一個(gè)類型參數(shù)在最前面哄啄,像函數(shù)聲明一樣:

function identity<T>(arg: T): T {
  return arg
}

let myIdentity: <T>(arg: T) => T = identity

我們也可以使用不同的泛型參數(shù)名雅任,只要在數(shù)量上和使用方式上能對(duì)應(yīng)上就可以。

function identity<T>(arg: T): T {
  return arg
}

let myIdentity: <U>(arg: U) => U = identity

我們還可以使用帶有調(diào)用簽名的對(duì)象字面量來定義泛型函數(shù):

function identity<T>(arg: T): T {
  return arg
}

let myIdentity: {<T>(arg: T): T} = identity

這引導(dǎo)我們?nèi)懙谝粋€(gè)泛型接口了增淹。我們把上面例子里的對(duì)象字面量拿出來做為一個(gè)接口:

interface GenericIdentityFn {
  <T>(arg: T): T
}

function identity<T>(arg: T): T {
  return arg
}

let myIdentity: GenericIdentityFn = identity

我們甚至可以把泛型參數(shù)當(dāng)作整個(gè)接口的一個(gè)參數(shù)椿访。 這樣我們就能清楚的知道使用的具體是哪個(gè)泛型類型(比如: Dictionary<string> 而不只是Dictionary)。這樣接口里的其它成員也能知道這個(gè)參數(shù)的類型了虑润。

interface GenericIdentityFn<T> {
  (arg: T): T
}

function identity<T>(arg: T): T {
  return arg
}

let myIdentity: GenericIdentityFn<number> = identity

注意成玫,我們的示例做了少許改動(dòng)。 不再描述泛型函數(shù)拳喻,而是把非泛型函數(shù)簽名作為泛型類型一部分哭当。 當(dāng)我們使用 GenericIdentityFn 的時(shí)候,還得傳入一個(gè)類型參數(shù)來指定泛型類型(這里是:number)冗澈,鎖定了之后代碼里使用的類型钦勘。對(duì)于描述哪部分類型屬于泛型部分來說,理解何時(shí)把參數(shù)放在調(diào)用簽名里和何時(shí)放在接口上是很有幫助的亚亲。

除了泛型接口彻采,我們還可以創(chuàng)建泛型類。 注意捌归,無法創(chuàng)建泛型枚舉和泛型命名空間肛响。

泛型類

泛型類看上去與泛型接口差不多。 泛型類使用( <>)括起泛型類型惜索,跟在類名后面特笋。

class GenericNumber<T> {
  zeroValue: T
  add: (x: T, y: T) => T
}

let myGenericNumber = new GenericNumber<number>()
myGenericNumber.zeroValue = 0
myGenericNumber.add = function(x, y) {
  return x + y 
}

GenericNumber 類的使用是十分直觀的,并且你可能已經(jīng)注意到了巾兆,沒有什么去限制它只能使用 number 類型猎物。 也可以使用字符串或其它更復(fù)雜的類型。

let stringNumeric = new GenericNumber<string>()
stringNumeric.zeroValue = ''
stringNumeric.add = function(x, y) { 
  return x + y
}

console.log(stringNumeric.add(stringNumeric.zeroValue, 'test'))

與接口一樣角塑,直接把泛型類型放在類后面蔫磨,可以幫助我們確認(rèn)類的所有屬性都在使用相同的類型。

我們?cè)?a href="/chapter2/class" target="_blank">類那節(jié)說過圃伶,類有兩部分:靜態(tài)部分和實(shí)例部分质帅。 泛型類指的是實(shí)例部分的類型,所以類的靜態(tài)屬性不能使用這個(gè)泛型類型留攒。

泛型約束

我們有時(shí)候想操作某類型的一組值,并且我們知道這組值具有什么樣的屬性嫉嘀。在 loggingIdentity 例子中炼邀,我們想訪問 arglength 屬性,但是編譯器并不能證明每種類型都有 length 屬性剪侮,所以就報(bào)錯(cuò)了拭宁。

function loggingIdentity<T>(arg: T): T {
  console.log(arg.length)
  return arg
}

相比于操作 any 所有類型洛退,我們想要限制函數(shù)去處理任意帶有 .length 屬性的所有類型。 只要傳入的類型有這個(gè)屬性杰标,我們就允許兵怯,就是說至少包含這一屬性。為此腔剂,我們需要列出對(duì)于 T 的約束要求媒区。

我們定義一個(gè)接口來描述約束條件,創(chuàng)建一個(gè)包含 .length 屬性的接口掸犬,使用這個(gè)接口和 extends 關(guān)鍵字來實(shí)現(xiàn)約束:

interface Lengthwise {
  length: number
}

function loggingIdentity<T extends Lengthwise>(arg: T): T {
  console.log(arg.length) // OK
  return arg
}

現(xiàn)在這個(gè)泛型函數(shù)被定義了約束袜漩,因此它不再是適用于任意類型:

loggingIdentity(3);  // Error

我們需要傳入符合約束類型的值,必須包含必須的屬性:

loggingIdentity({length: 10, value: 3}) // OK

在泛型約束中使用類型參數(shù)

你可以聲明一個(gè)類型參數(shù)湾碎,且它被另一個(gè)類型參數(shù)所約束宙攻。 比如,現(xiàn)在我們想要用屬性名從對(duì)象里獲取這個(gè)屬性介褥。 并且我們想要確保這個(gè)屬性存在于對(duì)象 obj 上座掘,因此我們需要在這兩個(gè)類型之間使用約束。

function getProperty<T, K extends keyof T> (obj: T, key: K ) {
  return obj[key]
}

let x = {a: 1, b: 2, c: 3, d: 4}

getProperty(x, 'a') // okay
getProperty(x, 'm') // error
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末柔滔,一起剝皮案震驚了整個(gè)濱河市溢陪,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌廊遍,老刑警劉巖嬉愧,帶你破解...
    沈念sama閱讀 217,734評(píng)論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異喉前,居然都是意外死亡没酣,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,931評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門卵迂,熙熙樓的掌柜王于貴愁眉苦臉地迎上來裕便,“玉大人,你說我怎么就攤上這事见咒〕ニィ” “怎么了?”我有些...
    開封第一講書人閱讀 164,133評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵改览,是天一觀的道長下翎。 經(jīng)常有香客問我,道長宝当,這世上最難降的妖魔是什么视事? 我笑而不...
    開封第一講書人閱讀 58,532評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮庆揩,結(jié)果婚禮上俐东,老公的妹妹穿的比我還像新娘跌穗。我一直安慰自己,他們只是感情好虏辫,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,585評(píng)論 6 392
  • 文/花漫 我一把揭開白布蚌吸。 她就那樣靜靜地躺著,像睡著了一般砌庄。 火紅的嫁衣襯著肌膚如雪羹唠。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,462評(píng)論 1 302
  • 那天鹤耍,我揣著相機(jī)與錄音肉迫,去河邊找鬼。 笑死稿黄,一個(gè)胖子當(dāng)著我的面吹牛喊衫,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播杆怕,決...
    沈念sama閱讀 40,262評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼族购,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼!你這毒婦竟也來了陵珍?” 一聲冷哼從身側(cè)響起寝杖,我...
    開封第一講書人閱讀 39,153評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎互纯,沒想到半個(gè)月后瑟幕,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,587評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡留潦,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,792評(píng)論 3 336
  • 正文 我和宋清朗相戀三年只盹,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片兔院。...
    茶點(diǎn)故事閱讀 39,919評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡殖卑,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出坊萝,到底是詐尸還是另有隱情孵稽,我是刑警寧澤,帶...
    沈念sama閱讀 35,635評(píng)論 5 345
  • 正文 年R本政府宣布十偶,位于F島的核電站菩鲜,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏惦积。R本人自食惡果不足惜接校,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,237評(píng)論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望荣刑。 院中可真熱鬧馅笙,春花似錦、人聲如沸厉亏。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,855評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽爱只。三九已至皿淋,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間恬试,已是汗流浹背窝趣。 一陣腳步聲響...
    開封第一講書人閱讀 32,983評(píng)論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留训柴,地道東北人哑舒。 一個(gè)月前我還...
    沈念sama閱讀 48,048評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像幻馁,于是被迫代替她去往敵國和親洗鸵。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,864評(píng)論 2 354

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