全名叫泛用型別,通用、多用途的意思,可以讓使用者在宣告函式或類別時,不用事先宣告好具體的型別,而是等到要呼叫的時候,再把型別帶進去就好了。這就使得我們的函式或類別在使用上更便利、更通用、但又能保有操作上的安全性。
今天只會講簡單的泛型介紹,讓我們開始吧,直接用一個例子來介紹:
function returnArg(arg:number):number{
return arg
}
這個函式很簡單,得到一個參數,並回傳那個參數,跟回音有點像。不過這邊的問題是,我們只能放入數字而已,用途(?)太侷限了。
那你一定也能想到:「那我們就把arg
跟回傳值都設為any
就好啦」。對,我們的確能這樣做,不過這樣就少掉決大部分的型別驗證了:
function returnArg(arg: any): any {
return arg
}
let result = returnArg("hello")
//下面這行在編譯時不會報錯唷,就算我們知道是字串,但因為是any,所以給過
result.toFixed(2)
我們既想要讓這函式方便後續使用,又不想少掉TypeScript幫我們檢查型別的功能(畢竟之後還可能會對result
進行操作),那要怎麼辦呢?這時透過泛型能夠幫助我們省很多事!他大概會長這樣:
function returnArg<TypeYouName>(arg: TypeYouName): TypeYouName{
return arg
}
//如果你有讀過某些套件的codebase,那麼你常看到的泛型名稱會是<T>
我們會用一個角括號(angle bracket)來代表泛型,裡面放的參數,其命名就跟一般函式參數的命名概念相仿,你就取一個有意義的名字即可,而我們這邊是TypeYouName
,所以後面不管是在函式的參數、或是回傳值,我們都能用TypeYouName
來指涉這個type。
所以,在呼叫時,我們可以這樣:
//游標hover在result1變數上時,就會看到 let result1: string
let result1 = returnArg<string>("hello")
//游標hover在result2變數上時,就會看到 let result1: number
let result2 = returnArg<number>(123)
//游標hover在result3變數上時,就會看到 let result3: {name: string}
let result3 = returnArg<{ name: string }>({ name: "John" })
//其實我們在這個例子也可以拋棄角括號,讓TypeScript幫我們從我們帶入的引數,去推論TypeYouName是什麼型別
let result4 = returnArg("放進去一下下就好")
//不過還是會建議主動加上角括號放入型別,這邊會順利只是因為例子很簡單
//要是函式引數是個更複雜的類型,TypeScript就有可能會錯誤的推論TypeYouName
讓我們再來看另一個例子:
function addId<T>(obj: T) {
let randomId = Math.floor(Math.random() * 100)
return { ...obj, randomId }
}
單純就程式碼來看,我們就只是要幫任何傳進來的物件引數,加上一個randomId
的屬性。要是我們這邊將字串給帶進去addId
呢?
let result = addId("hello")
字面上來看這完全沒有意義,我們不會想要為"hello"加上一個randomId
,但這邊編譯器不會報錯,我們甚至也真的得到一個物件,不過他長這樣lol:
{
'0': 'h',
'1': 'e',
'2': 'l',
'3': 'l',
'4': 'o',
randomId: 23(這是亂數生成的)
}
所以,我們可以將T
給延伸!變成這樣:
function addId<T extends object>(obj: T) {
let randomId = Math.floor(Math.random() * 100)
return { ...obj, randomId }
}
//類型 'string' 的引數不可指派給類型 'object' 的參數。
let result = addId("hello")
//or
function addId<T extends { name: string }>(obj: T) {
let randomId = Math.floor(Math.random() * 100)
return { ...obj, randomId }
}
//類型 'number' 不可指派給類型 'string'
let result2 = addId({ name: 18 })
泛型還能用在interface唷,來看一個interface例子:
interface Person<T> {
name: string
age: number
info: T
}
假設我們的info
想要讓他靈活一些,不要事先寫死,就能用上泛型來定義它:
interface Person<T> {
name: string
age: number
info: T
}
let john: Person<string> = {
name: "John",
age: 18,
//這邊就會報錯了,會寫string[]不能指派給類型string
info: ["polite"]
}
let allen: Person<string> = {
name: "Allen",
age: 20,
info: "polite" //正確不報錯
}
相信看到這邊,你已經知道泛型有多好用了,不過如果你是從零開始,突然看到別人的程式碼裡面,先是角括號又是一般括號,你一定會覺得:「這在寫什麼鬼」lol。
那我們明天再見!