在前一篇文章中,我們介紹了泛型的基本概念和它所提供的靈活性。今天,我們將深入探討泛型的多種應用方式,包括多個型別參數的泛型函式、泛型約束、泛型介面和泛型類別。讓我們一同探索這些進階主題。
泛型函式不僅僅可以有一個型別參數,還可以有多個型別參數。
function pair<T, U>(first: T, second: U): [U, T] {
return [second, first];
}
const result = pair(42, 'Hello');
console.log(result); // ['Hello', 42]
泛型函式是使用泛型型別的函式,它們使我們能夠建立通用的、可重用的函式,處理不同型別的輸入資料。
非泛型函式:
function merge(objA: object, objB: object) {
return { ...objA, ...objB };
}
const mergedObj = merge({ name: '肉鬆' }, { age: 30 });
mergedObj.name; // TypeScript 報錯
mergedObj.age; // TypeScript 報錯
儘管我們知道 mergedObj
有 name
跟 age
這兩個屬性,但 TypeScript 產生錯誤,這是因為它不提供任何有關返回物件的型別資訊。因此,在使用這個函式的結果時,我們無法確定返回的物件具有哪些屬性。
泛型函式:
function merge<T, U>(objA: T, objB: U) {
return { ...objA, ...objB };
}
const mergedObj = merge({ name: '肉鬆' }, { age: 30 });
console.log(mergedObj.name); // 肉鬆
console.log(mergedObj.age); // 30
在函式名稱後面使用了尖括號 <>
,引入了兩個泛型參數 T
和 U
。T
表示型別,而 U
表示另一個型別(這邊的 T
跟 U
可以自己命名)。最後函式的返回型別被定義為 T
與 U
的交集(T & U
),這意味著返回的物件將包含 T
和 U
兩種型別的屬性。
當然,我們也可以明確指定泛型參數的型別,但這裡我們不需要這麼做,因為 TypeScript 可以根據上下文進行型別推斷,使程式碼更簡潔。
function merge<T, U>(objA: T, objB: U) {
return { ...objA, ...objB };
}
const mergedObj = merge<{ name: string }, { age: number }>(
{ name: '肉鬆' },
{ age: 30 }
);
console.log(mergedObj.name); // 肉鬆
console.log(mergedObj.age); // 30
泛型約束是一種限制泛型型別的方法,使其只能是某些型別的子集。
function merge<T, U>(objA: T, objB: U) {
return { ...objA, ...objB };
}
const mergedObj = merge({ name: "肉鬆" }, 30); // 通過,TypeScript 不會報錯
在上面的範例中,我們將原本傳入 objB
的物件型別更改為 number
型別。在 JavaScript 中,這樣的操作是合法的,因為 JavaScript 是一種動態型別語言,允許在執行時更改變數的型別。因此,30(一個數字)可以成功分配給 objB
。
實際上,我們不關心具體的型別,也不關心泛型參數 T
和 U
提供的物件結構。 我們真正關心的是,這兩個參數,也就是型別 T
和 U
,應該要是物件,而不僅僅是任何型別。
讓我們改寫程式碼,使用 extends
約束型別:
function merge<T extends object, U extends object>(objA: T, objB: U) {
return { ...objA, ...objB };
}
const mergedObj1 = merge({ name: '肉鬆' }, 30);
// TypeScript 報錯,類型 'number' 的引數不可指派給類型 'object' 的參數
const mergedObj2 = merge({ name: '肉鬆' }, { age: 30 }); // 通過
泛型介面是一種定義可以處理多種不同型別資料的規範。使我們可以建立具有可變型別的自訂型別。
interface Pair<K, V> {
key: K;
value: V;
}
// 使用泛型介面來建立具體的實例
const numberPair: Pair<number, string> = { key: 1, value: 'one' };
const stringPair: Pair<string, boolean> = { key: 'isTrue', value: true };
// 定義一個函式,根據給定的 Pair 物件返回 value
function getValue<T, U>(pair: Pair<T, U>) {
return pair.value;
}
const result1 = getValue(numberPair);
const result2 = getValue(stringPair);
console.log(result1); // 'one'
console.log(result2); // true
在上面的範例中,我們定義了一個 Pair
介面,表示具有 key
和 value
的對應關係。然後,我們建立了兩個不同型別的 Pair
實例 numberPair
和 stringPair
。最後,我們定義了一個 getValue
函式,它接受一個 Pair
物件,並返回 value
。
泛型類別允許我們建立通用的類別定義,允許我們在實例化類別時指定不同的型別參數。
class Stack<T> {
items: T[] = [];
push(item: T) {
this.items.push(item);
}
pop(): T | undefined {
return this.items.pop();
}
}
const numberStack = new Stack<number>();
numberStack.push(1);
numberStack.push(2);
numberStack.push(3);
const stringStack = new Stack<string>();
stringStack.push('傑尼龜');
stringStack.push('肉鬆');
console.log(numberStack.pop()); // 3
console.log(stringStack.pop()); // '肉鬆'
在上面的範例中,我們定義了一個 Stack<T>
泛型類別,其中 T
是型別參數,表示堆疊可以處理的元素的型別。然後,我們可以使用 Stack<number>
和 Stack<string>
來分別建立處理數字和字串的堆疊實例。