TypeScript - 類型約束:發揮靈活類型的力量

你好,未來的 TypeScript 巫師們!今天,我們將踏上一段令人興奮的旅程,進入類型約束的世界。別擔心如果你是編程新手——我將成為你友好的導遊,我們將一步步攻克這個主題。在這個教學的結尾,你將能像專家一樣約束泛型!

TypeScript - Generic Constraints

什麼是類型約束?

在我們深入細節之前,讓我們從一個簡單的比喻開始。想像你有一個神奇的盒子,可以裝任何類型的物品。這就是 TypeScript 中的泛型——一個不同類型的靈活容器。現在,如果我們想要對這個盒子裡可以放什麼進行規範呢?這就是類型約束的用處!

類型約束允許我們限制可以用於我們泛型的類型。這就像在我們神奇的盒子上貼上一個標籤,上面寫著:“只允許具有 'length' 屬性的對象!”

問題示例:我們為什麼需要類型約束?

讓我們看看幾個類型約束可以拯救一天的場景:

示例 1:神秘的 Length 屬性

function getLength<T>(item: T): number {
return item.length; // 錯誤:屬性 'length' 在類型 'T' 上不存在
}

哦哦!TypeScript 給我們一個錯誤。為什麼?因為不是所有類型都有 length 屬性。如果我們將一個數字傳遞給這個函數會怎麼樣?數字沒有長度!

示例 2:混淆的比較

function compareValues<T>(value1: T, value2: T): boolean {
return value1 > value2; // 錯誤:運算符 '>' 不能應用於類型 'T' 和 'T'
}

又錯誤!TypeScript 不確定 T 是否可以使用 > 比較。如果我們傳遞字符串呢?或者複雜對象?

這些示例告訴我們為什麼我們需要類型約束。它們幫助我們寫出更精確且無錯誤的代碼。

TypeScript 中的類型約束如何工作

現在,讓我們看看如何使用類型約束來解決我們的問題:

神奇的 extends

為了添加約束,我們使用 extends 鍵。這就像告訴 TypeScript,“嘿,這個類型至少必須具備這些屬性或功能!”

讓我們修復我們的 getLength 函數:

interface Lengthwise {
length: number;
}

function getLength<T extends Lengthwise>(item: T): number {
return item.length; // 不再有錯誤!
}

現在,讓我們分解這個:

  1. 我們定義了一個接口 Lengthwise,它有一個 length 屬性。
  2. 我們使用 <T extends Lengthwise> 來說明 "T 必須至少有 Lengthwise 所具有的"。
  3. 現在 TypeScript 知道無論 T 是什麼,它肯定會有一個 length 屬性!

讓我們試一試:

console.log(getLength("Hello")); // 可以!字符串有長度
console.log(getLength([1, 2, 3])); // 可以!數組有長度
console.log(getLength(123)); // 錯誤!數字沒有長度

這不是很棒嗎?我們成功地約束了我們的泛型!

在類型約束中使用類型參數

有時候,我們想要根據另一個類型參數來約束一個類型參數。這就像說,“這個盒子只能裝與已經在裡面的東西兼容的物品。”

讓我們看一個例子:

function copyProperties<T extends U, U>(target: T, source: U): T {
for (let id in source) {
target[id] = source[id];
}
return target;
}

這裡發生了什麼?

  1. 我們有兩個類型參數:TU
  2. T extends U 意味著 T 必須至少是 U 的所有內容,但它可以有更多。
  3. 這允許我們從 source 複製屬性到 target,知道 target 將具有 source 的所有屬性。

讓我們看看它的運作:

interface Person {
name: string;
}

interface Employee extends Person {
employeeId: number;
}

let person: Person = { name: "Alice" };
let employee: Employee = { name: "Bob", employeeId: 123 };

copyProperties(employee, person); // 可以!
copyProperties(person, employee); // 錯誤!Person 沒有 employeeId

實際應用和最佳實踐

現在我們理解了類型約束如何工作,讓我們看看一些實際應用和最佳實踐:

  1. 約束為對象類型:通常,你會想要確保你正在處理對象:
function cloneObject<T extends object>(obj: T): T {
return { ...obj };
}
  1. 約束為函數類型:你可以確保一個類型是可調用的:
function invokeFunction<T extends Function>(func: T): void {
func();
}
  1. 約束為特定屬性:確保對象具有特定屬性:
function getFullName<T extends { firstName: string; lastName: string }>(obj: T): string {
return `${obj.firstName} ${obj.lastName}`;
}
  1. 多個約束:你可以使用 & 運算符應用多個約束:
function processData<T extends number & { toFixed: Function }>(data: T): string {
return data.toFixed(2);
}

這裡有一個總結這些方法的表格:

方法 描述 示例
對象約束 確保類型為對象 <T extends object>
函數約束 確保類型可調用 <T extends Function>
屬性約束 確保類型具有特定屬性 <T extends { prop: Type }>
多個約束 結合多個約束 <T extends TypeA & TypeB>

結論:擁抱約束的力量

恭喜你!你剛剛解鎖了 TypeScript 工具箱中的一個強大工具。類型約束讓我們能夠寫出靈活且類型安全的代碼,給我們兩者的最佳結合。

記住,掌握類型約束的關鍵是實踐。試著重構一些你現有的代碼以使用泛型和約束。你會驚訝於你的代碼變得有多乾淨和強健!

在我們結束之前,這裡有一個小編程幽默給你:為什麼 TypeScript 開發者破產了?因為他使用了太多的類型約束,無法接受任何類型的付款!?

持續編碼,持續學習,最重要的是,玩得開心和 TypeScript!

Credits: Image by storyset