TypeScript 進階:靈活應用泛型

前言
前一篇介紹了泛型的基本用法與核心概念。本文將深入探討泛型在進階應用中的強大能力,包括預設泛型型別、泛型約束、工具型別等,展示如何利用泛型來處理更複雜的型別操作。
泛型的預設型別
當你希望使用泛型時,有預設的型別,來增加程式的使用便利性,可以在泛型後面用 = 操作符來賦予型別給這個泛型。
class DataStorage<T = string> {
private data: T[] = []
addItem(item: T): void {
this.data.push(item)
}
getItems(): T[] {
return this.data
}
}
const textStorage = new DataStorage() // 預設型別為 string
textStorage.addItem('Hello')
console.log(textStorage.getItems()) // ['Hello']
const numberStorage = new DataStorage<number>()
numberStorage.addItem(0)
console.log(numberStorage.getItems()) // [0]這裡的 DataStorage 類別提供了一個預設泛型型別 string,如果在實例化時未指定型別,則會使用預設型別,這提升了使用的方便性。當然,你也可以在實例化時,指定具體的泛型型別。
如果沒預設泛型會怎樣
當這個例子如果沒有預設型別時,會因為在我們實例化類別時,TypeScript 沒辦法根據實例化的當下自動推斷這個泛型的型別,此時這個泛型就會是 unknown。
所以通常不預設泛型,就需要強迫使用這個類別的人,具體指定一個型別。

泛型約束
泛型約束允許你對泛型參數施加一定的限制,讓泛型變得更加精確可控。
在某些情況下,我們不僅需要泛型能接受多種型別,還希望限制泛型需要符合某些特定條件,可以使用 extends 來約束泛型的型別。
keyof 獲取物件屬性
keyof 關鍵字能讓我們獲取型別的屬性,搭配泛型約束,可以讓參數限制到只能選該型別的屬性。
function getProperty<
T extends Record<string, unknown>,
K extends keyof T
>(obj: T, key: K) {
return obj[key];
}
const person = { name: 'Johnson Mao', age: 18 };
constage name = getProperty(person, 'name'));
// name 是 string 型別在這裡,K 被限制為 T 物件的屬性鍵,保證在使用時,只能存取合法的屬性。這種模式經常用於需要動態存取物件屬性的函式中。
泛型約束的其他應用
你可以將泛型參數限制為實現特定屬性或方法的型別,例如:要求傳入的參數具有 length 屬性
interface Lengthwise {
length: number
}
function logLength<T extends Lengthwise>(item: T): void {
console.log(item.length)
}
logLength([1, 2, 3]) // 3
logLength('Hello') // 5
// logLength(10); // Error: 類型 'number' 的引數不可指派給類型 'Lengthwise' 的參數。這種泛型約束在處理陣列、字串等具備 length 屬性的型別時非常有用。
利用泛型的實用工具型別
在前面幾篇文章中,有介紹了 Record 與 Array,這些利用泛型來回傳型別的工具,
在實際開發中,還有很多好用的內建工具型別,這些工具型別利用了泛型的特性來簡化常見的型別操作。
Partial
將傳入的型別第一層屬性都改成可選的狀態,需注意嵌套的深層物件並不會變動唷!
interface PersonType {
name: string
box: {
height: number
width: number
}
}
/**
* Partial<PersonType> 回傳的型別等同於
*
* type PersonType = {
* name?: string;
* box?: {
* height: number;
* width: number;
* };
* };
*/
function updateObject<T>(obj: T, partialObj: Partial<T>): T {
return { ...obj, ...partialObj }
}
const person: PersonType = {
name: 'Johnson Mao',
box: {
height: 100,
width: 100,
},
}
updatePerson(person, { name: 'Mao' }) // 這樣就可以傳想修改的屬性了Required
是 Partial 的反向操作,適合用在需要確保物件完整性的場景,將傳入的型別第一層屬性都改成必有的狀態,同樣的需注意嵌套的深層物件並不會變動唷!
interface PersonType {
name?: string
box?: {
height?: number
width?: number
}
}
const person: Required<PersonType> = {
name: 'John',
box: {} // Required 只有第一層會改成必有的狀態,嵌套的則保持原狀
}Readonly
將傳入的型別第一層屬性設為唯讀,防止資料被意外修改,適合不可變資料結構的場景,同樣的需注意嵌套的深層物件並不會變動唷!
interface Ironman {
title: string
posts: { title: string, content: string }[]
}
const ironman: Readonly<Ironman> = {
title: 'TypeScript 完全指南:從語法基礎到高級功能的系統學習',
posts: []
}
// 因為 'title' 為唯讀屬性,所以無法指派至 'title'。
// ironman.title = '我想變更標題';
// 嵌套的物件屬性可以修改
ironman.posts[0] = {
title: 'TypeScript 進階:靈活應用泛型',
content: ''
}參考文獻
相關文章
TypeScript 後記
終於完成連續三十天的鐵人賽文章撰寫!距離我第一次參加鐵人賽已經三年之久了。這次參加主要是因為朋友邀請一起參賽,同時我自己也想藉此機會深入了解 TypeScript 5 的新功能,強迫自己透過寫作來吸收知識。
TypeScript 實戰:製作 npm Package
在開發專案時,經常會遇到跨專案共用邏輯或工具的情況,例如常用的 logger 或驗證工具。如果每次都從一個專案複製貼上到另一個專案,難免會出現版本不同步或維護困難的問題。為了解決這類問題,我們可以將這些共用邏輯獨立成為 npm 套件,以便在多個專案中重複使用。
TypeScript 實戰:前端表單與串接 API
在現代前端開發中,與後端進行資料交換是常見的需求。透過 HTTP 請求,我們可以將資料送出並取得結果。這篇文章將介紹如何使用 TypeScript 實作前端表單與串接 API,並展示如何將這些功能結合到 React 應用中。同時,文章還會介紹如何解決跨域問題(CORS),以便讓前後端能順利溝通。