TypeScript 泛型(Generic)
本節(jié)開始介紹 TypeScript 一些進階知識點,第一個要介紹的泛型是 TypeScript 中非常重要的一個概念,它是一種用以增強函數(shù)、類和接口能力的非常可靠的手段。
使用泛型,我們可以輕松地將那些輸入重復(fù)的代碼,構(gòu)建為可復(fù)用的組件,這給予了開發(fā)者創(chuàng)造靈活、可重用代碼的能力。
1. 慕課解釋
泛型在傳統(tǒng)的面向?qū)ο笳Z言中極為常見,可以使用泛型來創(chuàng)建可重用的組件,一個組件可以支持多種類型的數(shù)據(jù)。
通俗來講:泛型是指在定義函數(shù)、接口或者類時,未指定其參數(shù)類型,只有在運行時傳入才能確定。那么此時的參數(shù)類型就是一個變量,通常用大寫字母 T
來表示,當(dāng)然你也可以使用其他字符,如:U
、K
等。
語法:在函數(shù)名、接口名或者類名添加后綴 <T>
:
function generic<T>() {}
interface Generic<T> {}
class Generic<T> {}
2. 初識泛型
之所以使用泛型,是因為它幫助我們?yōu)椴煌愋偷妮斎?,?fù)用相同的代碼。
比如寫一個最簡單的函數(shù),這個函數(shù)會返回任何傳入它的值。如果傳入的是 number 類型:
function identity(arg: number): number {
return arg
}
如果傳入的是 string 類型:
function identity(arg: string): string {
return arg
}
通過泛型,可以把兩個函數(shù)統(tǒng)一起來:
function identity<T>(arg: T): T {
return arg
}
需要注意的是,泛型函數(shù)的返回值類型是根據(jù)你的業(yè)務(wù)需求決定,并非一定要返回泛型類型 T:
function identity<T>(arg: T): string {
return String(arg)
}
代碼解釋: 入?yún)⒌念愋褪俏粗?,但是通過 String 轉(zhuǎn)換,返回字符串類型。
3. 多個類型參數(shù)
泛型函數(shù)可以定義多個類型參數(shù):
function extend<T, U>(first: T, second: U): T & U {
for(const key in second) {
(first as T & U)[key] = second[key] as any
}
return first as T & U
}
代碼解釋: 這個函數(shù)用來合并兩個對象,具體實現(xiàn)暫且不去管它,這里只需要關(guān)注泛型多個類型參數(shù)的使用方式,其語法為通過逗號分隔 <T, U, K>
。
4. 泛型參數(shù)默認(rèn)類型
函數(shù)參數(shù)可以定義默認(rèn)值,泛型參數(shù)同樣可以定義默認(rèn)類型:
function min<T = number>(arr:T[]): T{
let min = arr[0]
arr.forEach((value)=>{
if(value < min) {
min = value
}
})
return min
}
console.log(min([20, 6, 8n])) // 6
解釋: 同樣的不用去關(guān)注這個最小數(shù)函數(shù)的具體實現(xiàn),要知道默認(rèn)參數(shù)語法為 <T = 默認(rèn)類型>
。
5. 泛型類型與泛型接口
先來回顧下之前章節(jié)介紹的函數(shù)類型:
const add: (x: number, y: number) => string = function(x: number, y: number): string {
return (x + y).toString()
}
等號左側(cè)的 (x: number, y: number) => string
為函數(shù)類型。
再看下泛型類型:
function identity<T>(arg: T): T {
return arg
}
let myIdentity: <T>(arg: T) => T = identity
同樣的等號左側(cè)的 <T>(arg: T) => T
即為泛型類型,它還有另一種帶有調(diào)用簽名的對象字面量書寫方式:{ <T>(arg: T): T }
:
function identity<T>(arg: T): T {
return arg
}
let myIdentity: { <T>(arg: T): T } = identity
這就引導(dǎo)我們?nèi)懙谝粋€泛型接口了。把上面例子里的對象字面量拿出來作為一個接口:
interface GenericIdentityFn {
<T>(arg: T): T
}
function identity<T>(arg: T): T {
return arg
}
let myIdentity: GenericIdentityFn = identity
進一步,把泛型參數(shù)當(dāng)作整個接口的一個參數(shù),我們可以把泛型參數(shù)提前到接口名上。這樣我們就能清楚的知道使用的具體是哪個泛型類型:
interface GenericIdentityFn<T> {
(arg: T): T
}
function identity<T>(arg: T): T {
return arg
}
let myIdentity: GenericIdentityFn<number> = identity
注意,在使用泛型接口時,需要傳入一個類型參數(shù)來指定泛型類型。示例中傳入了 number 類型,這就鎖定了之后代碼里使用的類型。
6. 泛型類
始終要記得,使用泛型是因為可以復(fù)用不同類型的代碼。下面用一個最小堆算法舉例說明泛型類的使用:
class MinClass {
public list: number[] = []
add(num: number) {
this.list.push(num)
}
min(): number {
let minNum = this.list[0]
for (let i = 0; i < this.list.length; i++) {
if (minNum > this.list[i]) {
minNum = this.list[i]
}
}
return minNum
}
}
代碼解釋: 示例中我們實現(xiàn)了一個查找 number 類型的最小堆類,但我們的最小堆還需要支持字符串類型,此時就需要泛型的幫助了:
// 類名后加上 <T>
class MinClass<T> {
public list: T[] = []
add(num: T) {
this.list.push(num)
}
min(): T {
let minNum = this.list[0]
for (let i = 0; i < this.list.length; i++) {
if (minNum > this.list[i]) {
minNum = this.list[i]
}
}
return minNum
}
}
let m = new MinClass<string>()
m.add('hello')
m.add('world')
m.add('generic')
console.log(m.min()) // generic
代碼解釋:
第 2 行,在聲明 類 MinClass
的后面后加上了 <T>
,這樣就聲明了泛型參數(shù) T,作為一個變量可以是字符串類型,也可以是數(shù)字類型。
7. 泛型約束
語法:通過 extends
關(guān)鍵字來實現(xiàn)泛型約束。
如果我們很明確傳入的泛型參數(shù)是什么類型,或者明確想要操作的某類型的值具有什么屬性,那么就需要對泛型進行約束。通過兩個例子來說明:
interface User {
username: string
}
function info<T extends User>(user: T): string {
return 'imooc ' + user.username
}
代碼解釋: 示例中,第 5 行,我們約束了入?yún)?user 必須包含 username 屬性,否則在編譯階段就會報錯。
下面再看另外一個例子:
type Args = number | string
class MinClass<T extends Args> {}
const m = new MinClass<boolean>() // Error, 必須是 number | string 類型
代碼解釋:
第 3 行,約束了泛型參數(shù) T 繼承自類型 Args,而類型 Args 是一個由 number 和 string 組成的聯(lián)合類型。
第 5 行,泛型參數(shù)只能是 number 和 string 中的一種,傳入 boolean 類型是錯誤的。
8. 多重類型泛型約束
通過 <T extends Interface1 & Interface2>
這種語法來實現(xiàn)多重類型的泛型約束:
interface Sentence {
title: string,
content: string
}
interface Music {
url: string
}
class Classic<T extends Sentence & Music> {
private prop: T
constructor(arg: T) {
this.prop = arg
}
info() {
return {
url: this.prop.url,
title: this.prop.title,
content: this.prop.content
}
}
}
代碼解釋:
第 10 行,約束了泛型參數(shù) T
需繼承自交叉類型(后續(xù)有單節(jié)介紹) Sentence & Music
,這樣就能訪問兩個接口類型的參數(shù)。
9. 小結(jié)
泛型在 TypeScript 中用途廣泛,可以靈活的控制類型之間的約束,提高代碼復(fù)用性,增強代碼可讀性。