TS - Generics泛型

一泪蔫、泛型

泛型(Generics)是指在定義函數(shù)、接口或類(lèi)的時(shí)候,不預(yù)先指定具體的類(lèi)型介评,而在使用的時(shí)候再指定類(lèi)型的一種特性。

1.1泛型的基本使用

首先實(shí)現(xiàn)一個(gè)函數(shù) getArr 爬舰,它創(chuàng)建一個(gè)數(shù)組们陆,將傳入的可變數(shù)量參數(shù)push到數(shù)組,并且將這個(gè)數(shù)組返回.
我限制了傳入?yún)?shù)的類(lèi)型string|number
傳入字符串'foo',''bar,并通過(guò)forEach方法獲取item的長(zhǎng)度

type Arr = (...args: (string | number)[]) => Array<string | number>
let getArr: Arr
getArr = (...args) => {
  const arr = []
  arr.push(...args)
  return arr
}
getArr('foo', 'bar').forEach(item => {
  item.length // “string | number”上不存在屬性“l(fā)ength”
})

但TS報(bào)錯(cuò)了,雖然邏輯上沒(méi)有錯(cuò)誤,但TypeScript 不確定一個(gè)聯(lián)合類(lèi)型的變量到底是哪個(gè)類(lèi)型的時(shí)候,我們只能訪問(wèn)此聯(lián)合類(lèi)型的所有類(lèi)型中共有的屬性或方法.返回的數(shù)組類(lèi)型是string|number,而number類(lèi)型的數(shù)據(jù)是沒(méi)有l(wèi)ength這個(gè)屬性的


此時(shí)可以使用類(lèi)型斷言情屹,將 item斷言成 string類(lèi)型

type Arr = (...args: (string | number)[]) => Array<string | number>
let getArr: Arr
getArr = (...args) => {
  const arr = []
  arr.push(...args)
  return arr
}
getArr('foo', 'bar').forEach(item => {
  (item as string).length
})

類(lèi)型斷言非常好用,但只能夠「欺騙」TypeScript 編譯器坪仇,無(wú)法避免運(yùn)行時(shí)的錯(cuò)誤,反而濫用類(lèi)型斷言可能會(huì)導(dǎo)致運(yùn)行時(shí)錯(cuò)誤.
有沒(méi)有其他辦法呢?


試著把返回?cái)?shù)組的類(lèi)型改成any呢

type Arr = (...args: (string | number)[]) => Array<any>
let getArr: Arr
getArr = (...args) => {
  const arr = []
  arr.push(...args)
  return arr
}
getArr('foo', 'bar').forEach(item => {
  item.length;// 3
  item.toFixed(1) // 正常編譯
})

更改后,可以正確獲取item.length,但是卻丟失了一些信息.item.toFixed()方法也能正常編譯了. 要知道toFixed()只能作用于number類(lèi)型的數(shù)據(jù).這顯然是不對(duì)的.
果然瀏覽器報(bào)錯(cuò)了.

  • error Uncaught TypeError: item.toFixed is not a function

這已經(jīng)失去了使用TS的初衷:在書(shū)寫(xiě)代碼時(shí)就發(fā)現(xiàn)錯(cuò)誤

有什么辦法可以讓返回的類(lèi)型與傳入的類(lèi)型保持一致呢

1.1.1 函數(shù)重載
function getArr(...args: string[]): Array<string>
function getArr(...args: number[]): Array<number>
function getArr(...args: (string | number)[]): Array<string | number> {
  const arr = []
  arr.push(...args)
  return arr
}
getArr('foo', 'bar').forEach(item => {
  item.length;// 3
  item.toFixed(1) // 報(bào)錯(cuò)  string上不存在error
})

通過(guò)函數(shù)重載,我指定了輸入輸出類(lèi)型一致.使其能夠正確的訪問(wèn)正確屬性,對(duì)不存在的屬性報(bào)錯(cuò)
但這樣寫(xiě)代碼未免太冗余了.

1.1.2 使用泛型
let getArr = function<T>(...args: T[]): Array<T> {
  const arr:T[] = [] //泛型變量可用于函數(shù)體中
  arr.push(...args)
  return arr
}
getArr<string>('foo', 'bar').forEach(item => {
  item.length;// 3
})

getArr<number>('foo', 'bar').forEach(item => {
  item.toFixed(1) // 正常編譯
})

在匿名函數(shù)名前添加了 <T>垃你, T 用來(lái)指代任意輸入的類(lèi)型椅文,通過(guò)泛型變量就可以使輸入輸出類(lèi)型保持一致.

在調(diào)用的時(shí)候喂很,可以在函數(shù)名后指定它具體的類(lèi)型為 string。當(dāng)然皆刺,也可以不手動(dòng)指定少辣,而讓類(lèi)型推論自動(dòng)推算.

1.2 多個(gè)泛型參數(shù)

定義泛型的時(shí)候,可以一次定義多個(gè)類(lèi)型參數(shù):

type Func = <T, U>(x: T, y: U) => [U, T]
let swap: Func = (x, y) => {
  return [y,x]
}
swap(1,'1'); // ['1',1]

函數(shù)swap返回一個(gè)元組,元組中的數(shù)據(jù)類(lèi)型與函數(shù)傳入?yún)?shù)類(lèi)型一致.

1.3 泛型約束

使用泛型時(shí),不預(yù)先指定具體的類(lèi)型芹橡,而在使用的時(shí)候再指定.

function foo<T>(x: T): T {
  return x 
}
foo<string>('foo').length

這未免有點(diǎn)與TypeScript優(yōu)點(diǎn)相悖,類(lèi)型限制shape應(yīng)該在定義而不是在使用時(shí).


此時(shí)如果把訪問(wèn)length屬性放在函數(shù)體中,

function foo<T>(x: T): T {
  x.length  // 類(lèi)型“T”上不存在屬性“l(fā)ength”
  return x 
}
foo('foo')

報(bào)錯(cuò)了,此時(shí)x的類(lèi)型為any,不是所有的類(lèi)型都有l(wèi)ength這個(gè)屬性,所以不能隨意的操作它的屬性或方法.
那么怎么在定義函數(shù)時(shí)給泛型類(lèi)型加以限制呢.
使用extends 關(guān)鍵字
(1)直接繼承具體類(lèi)型

function foo<T extends string>(x: T): T {
  x.length 
  return x
}
foo('foo')

此時(shí)才真正做到了輸入輸出類(lèi)型一致,并且限制了輸入類(lèi)型的shape
(2)繼承接口

interface Bar {
  length: number
}
function foo<T extends Bar>(x: T): T {
  x.length
  return x
}
foo('foo') // ?
foo({ length: 233 }) // ?

限制了泛型必須要有l(wèi)ength這個(gè)屬性.
(3)多個(gè)泛型參數(shù)之間相互約束

function foo<T, U extends keyof T>(obj: T, attr: U): T {
  console.log(obj[attr]);
  return obj
}
const bar = {
  name: 'bar'
}
foo(bar, 'name')
foo(bar, 'age') // error 類(lèi)型“"age"”的參數(shù)不能賦給類(lèi)型“"name"”的參數(shù)毒坛。

限制了泛型U類(lèi)型必須是T類(lèi)型的key

1.4 泛型默認(rèn)值

function foo<T = number>(x: T): T {
  return x
}

在 TypeScript 2.3 以后,我們可以為泛型中的類(lèi)型參數(shù)指定默認(rèn)類(lèi)型林说。當(dāng)使用泛型時(shí)沒(méi)有在代碼中直接指定類(lèi)型參數(shù)煎殷,從實(shí)際值參數(shù)中也無(wú)法推測(cè)出時(shí),這個(gè)默認(rèn)類(lèi)型就會(huì)起作用腿箩。

二豪直、泛型與接口

(1) interface
將1.1.2 使用泛型例子可用接口重構(gòu)

interface Arr {
  <T>(...args: T[]): Array<T>
}
let getArr: Arr
getArr = (...args) => {
  const arr = []
  arr.push(...args)
  return arr
}

也可以把泛型參數(shù)提前到接口名上,應(yīng)用于混合類(lèi)型接口

interface Arr<T>{
  (...args: T[]) :Array<T>
}
let getArr: Arr<any>  //定義類(lèi)型any
getArr = (...args) => {
  const arr = []
  arr.push(...args)
  return arr
}

注意,此時(shí)在使用泛型接口的時(shí)候珠移,需要定義泛型的類(lèi)型弓乙。且會(huì)出現(xiàn)unexpected error .慎用.


(2) type同理

type Arr = <T>(...args: T[]) => Array<T>

let getArr: Arr
getArr = (...args) => {
  const arr = []
  arr.push(...args)
  return arr
}

提取到接口名上也要定義泛型類(lèi)型,但其實(shí)多此一舉

type Arr<T> = (...args: T[]) => Array<T>

let getArr: Arr<any>= (...args) => {
  const arr = []
  arr.push(...args)
  return arr
}

三、泛型與類(lèi)

與泛型接口類(lèi)似钧惧,泛型也可以用于類(lèi)的類(lèi)型定義中.

class Person<T = string> {
  name: T
  constructor(name: T) {
    this.name = name
  }
  run(): T {
    const foo: T = this.name
    return foo
  }
}
const why = new Person<string>('why')
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末暇韧,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子浓瞪,更是在濱河造成了極大的恐慌懈玻,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,561評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件乾颁,死亡現(xiàn)場(chǎng)離奇詭異涂乌,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)英岭,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,218評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門(mén)湾盒,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人诅妹,你說(shuō)我怎么就攤上這事罚勾。” “怎么了吭狡?”我有些...
    開(kāi)封第一講書(shū)人閱讀 157,162評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵尖殃,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我赵刑,道長(zhǎng),這世上最難降的妖魔是什么场刑? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,470評(píng)論 1 283
  • 正文 為了忘掉前任般此,我火速辦了婚禮蚪战,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘铐懊。我一直安慰自己邀桑,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,550評(píng)論 6 385
  • 文/花漫 我一把揭開(kāi)白布科乎。 她就那樣靜靜地躺著壁畸,像睡著了一般。 火紅的嫁衣襯著肌膚如雪茅茂。 梳的紋絲不亂的頭發(fā)上捏萍,一...
    開(kāi)封第一講書(shū)人閱讀 49,806評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音空闲,去河邊找鬼令杈。 笑死,一個(gè)胖子當(dāng)著我的面吹牛碴倾,可吹牛的內(nèi)容都是我干的逗噩。 我是一名探鬼主播,決...
    沈念sama閱讀 38,951評(píng)論 3 407
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼跌榔,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼异雁!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起僧须,我...
    開(kāi)封第一講書(shū)人閱讀 37,712評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤纲刀,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后皆辽,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體柑蛇,經(jīng)...
    沈念sama閱讀 44,166評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,510評(píng)論 2 327
  • 正文 我和宋清朗相戀三年驱闷,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了耻台。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,643評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡空另,死狀恐怖盆耽,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情扼菠,我是刑警寧澤摄杂,帶...
    沈念sama閱讀 34,306評(píng)論 4 330
  • 正文 年R本政府宣布,位于F島的核電站循榆,受9級(jí)特大地震影響析恢,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜秧饮,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,930評(píng)論 3 313
  • 文/蒙蒙 一映挂、第九天 我趴在偏房一處隱蔽的房頂上張望泽篮。 院中可真熱鬧,春花似錦柑船、人聲如沸帽撑。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,745評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)亏拉。三九已至,卻和暖如春逆巍,著一層夾襖步出監(jiān)牢的瞬間及塘,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,983評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工蒸苇, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留磷蛹,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,351評(píng)論 2 360
  • 正文 我出身青樓溪烤,卻偏偏與公主長(zhǎng)得像味咳,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子檬嘀,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,509評(píng)論 2 348