iT邦幫忙

2022 iThome 鐵人賽

DAY 16
0

泛型Generic Types

全名叫泛用型別,通用、多用途的意思,可以讓使用者在宣告函式或類別時,不用事先宣告好具體的型別,而是等到要呼叫的時候,再把型別帶進去就好了。這就使得我們的函式或類別在使用上更便利、更通用、但又能保有操作上的安全性。

今天只會講簡單的泛型介紹,讓我們開始吧,直接用一個例子來介紹:

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。

那我們明天再見!


上一篇
第15天!再來看一點設定檔的例子!
下一篇
第17天!TypeScript 與 DOM的應用!
系列文
你也對開始使用typescript感到無力嗎?我也是 - 30天初探typescript30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言