TypeScript(六)泛型

泛型是指在定義函數(shù)宁改、接口或類的時(shí)候缕探,不預(yù)先指定具體的類型,而在使用的時(shí)候再指定類型的一種特性还蹲。一般用于解決類爹耗、方法、接口的復(fù)用性谜喊,以及對(duì)不特定數(shù)據(jù)類型的支持(類型校驗(yàn))潭兽。

創(chuàng)建一個(gè)函數(shù),要求函數(shù)的返回值就是函數(shù)的參數(shù)斗遏,參數(shù)類型是任意的山卦。可以通過如下方式實(shí)現(xiàn):

function echo(val: any): any {
  return val
}

以上使用any類型有幾個(gè)不足之處:首先any類型ts會(huì)放棄類型檢查最易,失去使用ts的初衷及優(yōu)勢怒坯;其次,既然返回值是any類型藻懒,并不能準(zhǔn)確的定義返回值的類型剔猿。此時(shí)泛型就派上用場了。

1. 泛型函數(shù)

實(shí)現(xiàn)一個(gè)函數(shù)嬉荆,函數(shù)接收兩個(gè)參數(shù)归敬,返回值是參數(shù)1為length,參數(shù)2為元素組成的數(shù)組鄙早。

function createArray<T>(length: number, value: T): Array<T> {
  const result: T[] = []
  for (let i = 0; i < length; i++) {
      result[i] = value
  }
  return result
}

以上就是一個(gè)泛型函數(shù)汪茧, T 代表任意類型。

泛型函數(shù)調(diào)用方式:

  1. 傳入所有參數(shù)限番,包括類型參數(shù)
createArray<string>(3, 'x') // ['x', 'x', 'x']
  1. 利用類型推論舱污,直接傳入?yún)?shù)
createArray(3, 'x')

多個(gè)類型參數(shù)

實(shí)現(xiàn)一個(gè)函數(shù),參數(shù)是長度為2弥虐、元素為任意類型的元組扩灯,返回交換位置后的元組媚赖。

function exchange<T, U>(tuple: [T, U]): [U, T] {
    return [tuple[1], tuple[0]]
}

exchange([7, 'sina']); // ['sina', 7]

通過類型別名定義泛型函數(shù)

type IGetArr<T> = (val: T, length: number) => T[]
const getArr: IGetArr<number> = (val: number, length: number = 3): number[] => {
  return Array(length).fill(val)
}
getArr('4', 5)

2. 泛型約束

在泛型函數(shù)內(nèi)部使用泛型變量時(shí),由于我們預(yù)先并不知道它是哪種類型珠插,所以不能隨意操作其屬性和方法(比如:獲取泛型變量的length)惧磺,但 T 不一定包含該屬性,所以編譯器會(huì)報(bào)錯(cuò)捻撑。

function getLength<T>(arg: T): T {
  console.log(arg.length) // Property 'length' does not exist on type 'T'.
  return arg
}

此時(shí)就需要對(duì)泛型變量進(jìn)行約束磨隘,要求其必須包含length屬性,這種操作我們稱之為泛型約束顾患。

定義接口來描述約束條件番捂,并用 extends 關(guān)鍵字實(shí)現(xiàn)約束。

如下泛型函數(shù)的參數(shù)必須具有l(wèi)ength屬性:

interface restrainLength {
  length: number
}

function getLength<T extends restrainLength>(arg: T): T {
  console.log(arg.length) 
  return arg
}

多個(gè)參數(shù)時(shí)也可以在泛型約束中使用類型參數(shù)

多個(gè)參數(shù)的泛型函數(shù)描验,一個(gè)類型參數(shù)被另一類型參數(shù)所約束白嘁。

定義一個(gè)函數(shù), 接受兩個(gè)參數(shù) 第一個(gè)是對(duì)象 obj膘流,第二個(gè)是第一參數(shù)對(duì)象中的key絮缅,返回 obj[key]。

使用 keyof 關(guān)鍵字約束呼股。

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

const obj = { a: 1, b: 2, c: 3 }
getValue(obj, 'a')
getValue(obj, 'd') //  Argument of type '"d"' is not assignable to parameter of type '"a" | "b" | "c"'.
keyof 索引類型查詢操作符

對(duì)應(yīng)任何類型T,keyof T的結(jié)果為該類型上所有公共屬性名的聯(lián)合:

interface Eg1 {
  name: string,
  readonly age: number,
}
// T1的類型實(shí)則是 "name" | "age"
type T1 = keyof Eg1

class Eg2 {
  private name: string;
  public readonly age: number;
  protected home: string;
}
// T2實(shí)則被約束為 "age"
// 因?yàn)閚ame和home不是公有屬性耕魄,所以不能被keyof獲取到
type T2 = keyof Eg2
interface Eg1 {
  name: string,
  readonly age: number,
}

interface Eg2 {
  sex: string
}
// T1的類型實(shí)則是 "name" | "age" | { sex: string }
type T1 = keyof Eg1 | Eg2

let a: T1 = "name"; // OK
let b: T1 = "age"; // OK
let c: T1 = { // OK
  sex: "男"
}

注意:keyof any 的結(jié)果為 string | number | symbol,因?yàn)閷?duì)象的key常見的類型就是這三種

TypeScript 2.8 作用于交叉類型的keyof被轉(zhuǎn)換成作用于交叉成員的keyof的聯(lián)合彭谁。 換句話說吸奴,keyof (A & B)會(huì)被轉(zhuǎn)換成keyof A | keyof B。 這個(gè)改動(dòng)應(yīng)該能夠解決keyof表達(dá)式推斷不一致的問題缠局。

type A = { a: string };
type B = { b: string };
type T1 = keyof (A & B);  // "a" | "b"
type T2<T> = keyof (T & B);  // keyof T | "b"
type T3<U> = keyof (A & U);  // "a" | keyof U
type T4<T, U> = keyof (T & U);  // keyof T | keyof U
type T5 = T2<A>;  // "a" | "b"
type T6 = T3<B>;  // "a" | "b"
type T7 = T4<A, B>;  // "a" | "b"

3. 泛型接口

可以通過接口的方式定義函數(shù)需要遵循的約束和規(guī)范则奥。

interface IFoo {
  (name: string): string
}

let getInfo: IFoo = function(name: string): string{
  return `the name is ${name}` 
}

這種方式不具有普適性,name的類型被限制死了狭园《链Γ可以通過含有泛型的接口定義函數(shù),如下:

interface IFoo {
  <T>(length: number, val: T): Array<T>
} 
const getArr: IFoo = function<T>(length: number, val: T): Array<T> {
  return Array(length).fill(val)
}
console.log(getArr(3, 'sian')) // ["sian", "sian", "sian"]

把泛型參數(shù)提前到接口名上唱矛,就可以知道使用的具體是哪個(gè)泛型類型罚舱,這樣接口里的其他成員也能知道這個(gè)參數(shù)的類型。

interface IFoo<T> {
  (length: number, val: T): Array<T>;
}

const getFoo: IFoo<string> = function (
    length: number,
    val: string
  ): [string, number] {
    return [val, length]
  }

function getArr<T>(length: number, val: T): Array<T> {
  return Array(length).fill(val)
}

const getFoo: IFoo<string> = getArr
getFoo(3, 'sina')  // ['sina', 'sina', 'sina']

注意: 此時(shí)使用泛型接口時(shí)绎谦, 需要定義泛型的類型

4. 泛型類

與泛型接口類似管闷,泛型也可以用于類的類型定義中,泛型類使用 <> 括起泛型類型窃肠,跟在類名后面:

class Test<T> {
  foo: T
  constructor(foo: T) {
    this.foo = foo
  }
  echo(val: T): T {
    return val
  }
}

const test = new Test<number>(12)
test.echo(3)

泛型參數(shù)的默認(rèn)類型

可以為泛型中的類型參數(shù)指定默認(rèn)類型包个。當(dāng)使用泛型時(shí)沒有在代碼中直接指定類型參數(shù),從實(shí)際值參數(shù)中也無法推測出時(shí)冤留,這個(gè)默認(rèn)類型就會(huì)起作用赃蛛。

function createArray<T = string>(length: number, value: T): Array<T> {
  const result: T[] = []
  for (let i = 0; i < length; i++) {
      result[i] = value
  }
  return result
}
createArray<number>(3, 3)

在.tsx中使用尾隨逗號(hào)

只有一個(gè)泛型參數(shù)時(shí)恃锉,尾隨逗號(hào)是必須的。否則會(huì)出現(xiàn)編譯錯(cuò)誤呕臂。


image.png
const returnInArray = <T,>(value: T): T[] => {
  return [value];
};

const strArray = returnInArray<string>('hello');
const numArray = returnInArray<number>(360);

如果有多種類型,則不必使用尾隨逗號(hào)肪跋,例如:<T, Y>
如果使用了尾隨逗號(hào)文件還報(bào)錯(cuò)歧蒋,可以擴(kuò)展一個(gè)空對(duì)象。

const carr = <T extends unknown>(val: T): T[] => {
    return [val]
}

carr<string>('3')
carr(4)

T extends unknown 約束不執(zhí)行任何操作州既,并且僅當(dāng)處理.tsx文件時(shí)出現(xiàn)語法錯(cuò)誤時(shí)才需要該約束谜洽。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市吴叶,隨后出現(xiàn)的幾起案子阐虚,更是在濱河造成了極大的恐慌,老刑警劉巖蚌卤,帶你破解...
    沈念sama閱讀 217,277評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件实束,死亡現(xiàn)場離奇詭異,居然都是意外死亡逊彭,警方通過查閱死者的電腦和手機(jī)咸灿,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來侮叮,“玉大人避矢,你說我怎么就攤上這事∧野瘢” “怎么了审胸?”我有些...
    開封第一講書人閱讀 163,624評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長卸勺。 經(jīng)常有香客問我砂沛,道長,這世上最難降的妖魔是什么孔庭? 我笑而不...
    開封第一講書人閱讀 58,356評(píng)論 1 293
  • 正文 為了忘掉前任尺上,我火速辦了婚禮,結(jié)果婚禮上圆到,老公的妹妹穿的比我還像新娘怎抛。我一直安慰自己,他們只是感情好芽淡,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,402評(píng)論 6 392
  • 文/花漫 我一把揭開白布马绝。 她就那樣靜靜地躺著,像睡著了一般挣菲。 火紅的嫁衣襯著肌膚如雪富稻。 梳的紋絲不亂的頭發(fā)上掷邦,一...
    開封第一講書人閱讀 51,292評(píng)論 1 301
  • 那天,我揣著相機(jī)與錄音椭赋,去河邊找鬼抚岗。 笑死,一個(gè)胖子當(dāng)著我的面吹牛哪怔,可吹牛的內(nèi)容都是我干的宣蔚。 我是一名探鬼主播,決...
    沈念sama閱讀 40,135評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼认境,長吁一口氣:“原來是場噩夢啊……” “哼胚委!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起叉信,我...
    開封第一講書人閱讀 38,992評(píng)論 0 275
  • 序言:老撾萬榮一對(duì)情侶失蹤亩冬,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后硼身,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體硅急,經(jīng)...
    沈念sama閱讀 45,429評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,636評(píng)論 3 334
  • 正文 我和宋清朗相戀三年鸠姨,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了铜秆。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,785評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡讶迁,死狀恐怖连茧,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情巍糯,我是刑警寧澤啸驯,帶...
    沈念sama閱讀 35,492評(píng)論 5 345
  • 正文 年R本政府宣布,位于F島的核電站祟峦,受9級(jí)特大地震影響罚斗,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜宅楞,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,092評(píng)論 3 328
  • 文/蒙蒙 一针姿、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧厌衙,春花似錦距淫、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春彤枢,著一層夾襖步出監(jiān)牢的瞬間狰晚,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評(píng)論 1 269
  • 我被黑心中介騙來泰國打工缴啡, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留壁晒,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,891評(píng)論 2 370
  • 正文 我出身青樓盟猖,卻偏偏與公主長得像讨衣,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子式镐,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,713評(píng)論 2 354

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