在前面的章節中,我們介紹了如何直接指定類型,這種方式在實務中非常常見。但是,當我們有多個相似功能的函式或物件時,重複定義類型並不是最理想的解決方案。
或許你會想到:再定義一個不同型別的函式或物件不就好了?但我們都知道,程式碼應該避免不必要的重複,這樣可以提升效能並降低維護成本。在這種情況下,泛型(Generic)就能派上用場了。
維基百科是這麼介紹泛型的:泛型程式設計(英文:generic programming)是程式設計語言的一種風格或範式。泛型允許程式設計師在強型別程式設計語言中編寫代碼時使用一些以後才指定的類型,在實例化時作為參數指明這些類型。
泛型的寫法是用 <>
來表示,並在裡面放入一個類型變數,通常命名為 T
(意指 Type),雖然名稱沒有強制規定,但 T
是一種常見的慣用命名。
下方範例中,我們定義了一個 Data
型別,該型別包含一個 content
屬性和一個 addItem
方法,但具體的類型尚未確定,因此我們用 T
來表示:
type Data<T> = {
content: T[]
addItem: (item: T) => void
}
透過這個泛型定義,我們可以靈活地將不同的資料類型傳入。以下是字串型別的例子:
type Data<T> = {
content: T[]
addItem: (item: T) => void
}
const stringData: Data<string> = {
content: [],
addItem(item: string) {
this.content.push(item)
},
}
stringData.addItem('Hello')
console.log(stringData.content) // ['Hello']
同樣,我們也可以使用數字型別:
type Data<T> = {
content: T[]
addItem: (item: T) => void
}
const numberData: Data<number> = {
content: [],
addItem(item: number) {
this.content.push(item)
},
}
numberData.addItem(1)
console.log(numberData.content) // [1]
函式也可以使用泛型來達到類似的效果。只需要將型別包在 <>
中,並將類型變數分配給參數即可:
function restaurant<T, U>(a: T, b: U) {
console.log({ ...a, ...b })
}
const restaurant1 = restaurant<{ name: string }, { phone: number }>(
{ name: 'Subway' },
{ phone: 1234567890 }
)
console.log(restaurant1) // { name: 'Subway', phone: 1234567890 }
搭配上面這個例子,如果我們想再加入一個 location 參數呢?你會怎麼做呢?
最一開始的我,想說這題簡單我會,我就這麼做了:
function restaurant<T, U, T>(a: T, b: U, c: T) {
console.log({ ...a, ...b, ...c })
}
const restaurant1 = restaurant<
{ name: string },
{ phone: number },
{ location: string }
>({ name: 'Subway' }, { phone: 1234567890 }, { location: 'Taiwan' })
結果我得到了一片紅紅的錯誤訊息:
沒有錯!函式使用了重複的泛型參數 T
,這在 TypeScript 中是無效的。每個泛型參數應該具有唯一的名稱。範例中定義了兩個 T
,這是導致錯誤的原因。
正確的寫法如下:
function restaurant<T, U, V>(a: T, b: U, c: V) {
console.log({ ...a, ...b, ...c })
}
const restaurant1 = restaurant<
{ name: string },
{ phone: number },
{ location: string }
>({ name: 'Subway' }, { phone: 1234567890 }, { location: 'Taiwan' })