Skip to content

建立 2026-09-15 更新 2026-09-15

泛型

泛型(Generics)讓你寫一份函式或型別定義,套用到多種不同型別上,同時保留型別檢查——不用犧牲安全性換取重用性,也不用退回 any

為什麼需要泛型

先看沒有泛型會遇到的問題:

function firstAny(arr: any[]): any {
  return arr[0]
}

const num = firstAny([1, 2, 3])       // 型別是 any,失去型別檢查
num.toUpperCase()                      // 編譯不會報錯,但執行期會噴錯

any 雖然能處理任何陣列,但回傳值也變成 any,等於放棄了型別檢查。泛型可以保留「輸入是什麼型別,輸出就是什麼型別」的關聯:

function first<T>(arr: T[]): T {
  return arr[0]
}

const num = first([1, 2, 3])          // T 被推論為 number,num 是 number
const str = first(["a", "b"])         // T 被推論為 string,str 是 string
// num.toUpperCase()                  // 編譯錯誤:number 沒有 toUpperCase

<T> 是型別參數,呼叫時 TypeScript 會依傳入的引數自動推論出 T 是什麼型別,也可以手動指定:first<number>([1, 2, 3])

泛型 interface 與 type

interface ApiResponse<T> {
  data: T
  status: number
}

const userResponse: ApiResponse<{ id: number; name: string }> = {
  data: { id: 1, name: "Alice" },
  status: 200,
}

API 回應的外層結構(datastatus)都一樣,只有 data 裡面的實際資料型別不同——這正是泛型最常見的使用情境:一份 ApiResponse 定義,套用到所有 API。

泛型限制(Constraints)

interface HasLength {
  length: number
}

function logLength<T extends HasLength>(item: T): T {
  console.log(item.length)
  return item
}

logLength("hello")       // OK,字串有 length
logLength([1, 2, 3])     // OK,陣列有 length
// logLength(123)        // 編譯錯誤:number 沒有 length

extends 在這裡是限制 T 必須符合某個形狀,不是類別繼承。沒有限制時 T 可以是任何型別,導致你在函式裡幾乎不能對 T 做任何操作;加上 extends HasLength,TypeScript 才知道 item 一定有 .length 可以用。

相關影音

Net Ninja 這支專講泛型,從「為什麼需要泛型」講到泛型函式與泛型 interface,對應本頁全部內容,已嵌在 進階型別總覽