TypeScript 中的泛型使開發者能夠編寫靈活、可重用的代碼,同時保持類型安全。它們允許動態定義類型,確保函數、類和接口可以適用于任何數據類型。這有助于避免重復,提高代碼的模塊化,使其既類型安全又具備適應性。
一、認識泛型
當程序員需要創建可重用組件時,可以使用 TypeScript 的泛型類型,因為它們用于創建可以處理各種數據類型的組件,并提供類型安全性。可重用組件可以是類、函數和接口。TypeScript 泛型可以以多種方式使用,例如函數泛型、類泛型和接口泛型。
(一) 語法
function functionName<T> (returnValue : T) : T {return returnValue;
}
(二) 用法示例
1. 例一
在這個例子中,我們創建了一個可以接受任意類型數據的泛型函數。我們只需要在參數中傳入任何類型的數據,然后就可以通過 reverseArray
函數對該值進行反轉。
function reverseArray<T>(array: T[]): T[] {return array.reverse();
}const strArray: string[] = reverseArray(["Java", "Python", "C++"]);
const numArray: number[] = reverseArray([1, 2, 3, 4, 5]);
const boolArray: boolean[] = reverseArray([false, true]);console.log(strArray);
console.log(numArray);
console.log(boolArray);
輸出:
[ 'C++', 'Python', 'Java' ]
[ 5, 4, 3, 2, 1 ]
[ true, false ]
2. 例二
在這個例子中,我們創建了一個泛型接口,通過它我們創建了一個對象和字符串類型的值,并將它們打印在控制臺中。
// 定義一個泛型接口 Resource,T 是類型參數
interface Resource<T> {id: number; // 資源 IDresourceName: string; // 資源名稱data: T; // 泛型數據字段,類型由使用者決定
}// 使用對象類型實現泛型接口
const person: Resource<object> = {id: 1, // 資源 ID 為 1resourceName: 'Person', // 資源名稱為 "Person"data: { // data 是一個對象類型name: 'John', // 姓名age: 25 // 年齡}
}
console.log(person); // 打印 person 對象// 使用字符串數組類型實現泛型接口
const employee: Resource<string[]> = {id: 2, // 資源 ID 為 2resourceName: 'Employee', // 資源名稱為 "Employee"data: ['Employee 1', 'Employee 1'] // data 是一個字符串數組
}
console.log(employee); // 打印 employee 對象
輸出:
{"id": 1,"resourceName": "Person","data": {"name": "John","age": 25}
}
{"id": 2,"resourceName": "Employee","data": ["Employee 1","Employee 1"]
}
二、泛型函數
TypeScript 泛型函數允許你創建可以處理多種類型的數據的函數,同時保持類型安全。通過使用尖括號(<T>
)中定義的類型參數,泛型使函數能夠在不同的數據類型上運行,而不會失去 TypeScript 類型檢查所帶來的優勢。
(一) 語法
function functionName<T>(parameterName: T): ReturnType {// 函數實現
}
這里:
functionName
是你的泛型函數的名稱。<T>
是類型參數T
,它允許你在函數內部處理不同的數據類型。parameterName
表示函數參數的名稱,T
指定該參數接受的數據類型。ReturnType
指定函數預期返回值的類型。
(二) 用法示例
1. 例一
TypeScript 中帶有單個類型參數的泛型函數能夠處理多種數據類型,同時保證類型安全。你可以顯式指定類型,也可以讓 TypeScript 推斷參數和返回值的類型。
function foo<T>(arg: T): T {return arg;
}let result1: string = foo<string>("TYPESCRIPT");
let result2: number = foo<number>(740);
let result3: boolean = foo<boolean>(false);console.log(result1);
console.log(result2);
console.log(result3);
輸出:
GEEKSFORGEEKS
740
false
2. 例二
帶有數組參數的泛型函數允許你處理不同類型的數組。通過使用類型參數,該函數可以處理各種元素類型的數組,同時保持類型安全。
function arrayEl<T>(arr: T[]): void {for (const x of arr) {console.log(x);}
}let elements: number[] = [101, 102, 103];
arrayEl(elements);let elements1: string[] = ["吃飯", "睡覺", "打豆豆"];
arrayEl(elements1);
輸出:
101
102
103
吃飯
睡覺
打豆豆
3. 例三
在這個例子中,函數 mergeArrays 將兩種不同類型的數組合并為一個。它接收類型為 T 和 U 的數組,返回類型為 (T | U)[] 的數組。
function mergeArrays<T, U>(arr1: T[], arr2: U[]): (T | U)[] {return [...arr1, ...arr2];
}// 不同類型的數組
const numbers: number[] = [1, 2, 3];
const words: string[] = ["hello", "world"];// 合并不同類型的數組
const mergedArray: (number | string)[] = mergeArrays(numbers, words);// 輸出合并后的數組
console.log(mergedArray);
輸出:
[1, 2, 3, "hello", "world"]
(三) 總結
在 TypeScript 中,泛型函數允許你構建能夠處理不同數據類型的通用函數,同時確保代碼的類型安全,避免錯誤。這種靈活性非常實用,尤其是在你希望創建能夠應對多種情況而無需重復代碼的函數時。無論是處理數組、對象還是其他數據結構,泛型函數都能幫助你編寫更簡潔、更安全的代碼。
三、泛型約束
在 TypeScript 中,泛型約束(Generic Constraints)通過使用 extends
關鍵字來限制可以與泛型類型一起使用的類型。這確保了泛型類型遵循特定的結構或接口,從而能夠在泛型中訪問某些屬性或方法。
(一) 什么是泛型約束?
- TypeScript 的泛型使我們能夠編寫可重用的代碼,能夠處理不同的數據類型。我們可以創建可以與不同數據類型一起工作的函數、類或接口。
- 泛型使用
<T>
的形式定義,這種 T 類型可以用于函數參數、返回值等的類型定義。 - 泛型約束用于對泛型類型參數可使用的類型施加限制。
- 這種限制帶來了類型檢查,確保變量在被使用之前具備某種類型的值。
- 這種檢查機制有助于最大限度地減少運行時錯誤的發生。
語法:
function genericConstraintFunction<T extends GenericConstraintType>(param: T): void {// ...
}
含義如下:
- T 是泛型類型參數。
extends
GenericConstraintType 指定了一個約束條件,要求類型參數 T 必須繼承或符合 GenericConstraintType 類型的結構。
(二) 用法示例
1. 例一
在此示例中,Sports
接口具有一個 name
屬性。printSportName
函數使用 extends
來確保其參數在執行前符合 Sports
類型的結構。
// 使用 name 屬性定義 Sports 接口
interface Sports {name: string;
}// 定義函數,確保傳入的類型 T 繼承自 Sports
function printSportName<T extends Sports>(sport: T): void {console.log(sport.name); // 輸出運動名稱
}// 創建一個 Sports 類型的對象
let sport: Sports = { name: "棒球" };// 使用 sport 對象調用函數
printSportName(sport);
輸出:
baseball
2. 例二
在此示例中,我們使用 extends keyof
來確保泛型類型參數 K
是類型 T
的一個鍵。這種做法強制要求 K
必須是 T
的一個有效屬性。
interface Sports {name: string;players: number;
}// 泛型函數,限制 T 必須擴展自 Sports,K 必須是 T 的鍵
function getNumberOfPlayers<T extends Sports, K extends keyof T>(sport: T, players: K): T[K] {return sport[players];}let sport: Sports = { name: "baseball", players: 9 };// 'players' 被推斷為 number 類型
let players: number = getNumberOfPlayers(sport, 'players');
console.log(`球員人數為:${players}`);
輸出:
球員人數為:9
3. 例三
在此示例中,我們確保泛型類型參數的類對象實現了特定的接口。
// 定義一個接口 Sports
interface Sports {name: string;players: number;getNumberOfGloves(): number; // 獲取手套數量的方法
}// 定義一個實現 Sports 接口的類 Baseball
class Baseball implements Sports {constructor(public name: string, public players: number) { }// 實現接口中的方法getNumberOfGloves(): number {return this.players * 2;}
}// 定義一個泛型函數,約束類型 T 必須實現 Sports 接口
function getNumberOfGloves<T extends Sports>(sport: T): void {console.log(`所需手套數量為:${sport.getNumberOfGloves()}`);
}// 創建 Baseball 類的實例
let baseball = new Baseball("baseball", 9);
getNumberOfGloves(baseball); // 輸出:所需手套數量為:18
輸出:
所需手套數量為:18
四、泛型接口
在 TypeScript 中創建帶有泛型類型的接口是一項強大的功能,它提升了代碼的靈活性和可復用性。通過定義帶有泛型類型參數的接口,你可以創建適用于多種數據類型的組件,同時保持類型安全。
(一) 帶有單一泛型類型參數的接口
interface InterfaceName<T> {// 接口成員
}
定義一個帶有單一泛型類型參數的接口,可以創建通用組件,這些組件能夠在不同數據類型間靈活操作。此方法提升了代碼的靈活性,并通過抽象具體的數據實現,促進代碼的重用。
下面的代碼創建了一個帶有單個泛型類型參數的接口。
interface Box<T> {getItem(): T;setItem(item: T): void;
}class MyBox<T> implements Box<T> {private item: T;getItem(): T {return this.item;}setItem(item: T): void {this.item = item;}
}const stringBox: Box<string> = new MyBox<string>();
stringBox.setItem("Hello, Generics!");
console.log("字符串的值:", stringBox.getItem());
輸出:
字符串的值:Hello, Generics!
(二) 多泛型類型參數
TypeScript 也支持帶有多個泛型類型參數的接口,使開發者能夠設計高度可定制且參數化的組件。
下面的代碼創建了一個帶有多個泛型類型參數的接口。
interface Pair<K, V> {key: K;value: V;
}const pair: Pair<number, string> = { key: 1, value: "One" };
console.log("Key:", pair.key, ", Value:", pair.value);
輸出:
Key: 1, Value: One
五、泛型與類
TypeScript 中在泛型中使用類類型,可以讓你通過指定泛型參數為類的構造函數,創建更靈活且可重用的代碼。當你想操作類的實例,同時又希望保持類型安全時,這種方式特別有用。你可以定義一個接受類構造函數的泛型類型,然后使用該類型來創建和操作這些類的實例。
(一) 語法
function createInstance<T>(constructor: new (...args: any[]) => T,...args: any[]
): T {// 創建并返回指定類的實例return new constructor(...args);
}
說明:
- T 是一個泛型類型參數,表示要創建的實例的類型。它允許你指定工廠函數的期望返回類型。
- constructor 是類構造函數的引用。
- new (...args: any[]) => T 表示該構造函數可以接受任意數量的參數(
...args: any[]
),并返回類型為T
的實例。 - ...args: any[] 使用剩余參數語法,接受任意數量的額外參數,這些參數會在創建實例時傳遞給構造函數。
(二) 用法示例
1. 例一
在這個例子中,我們定義了一個名為 createInstance 的泛型工廠函數。它接受一個類的構造函數(constructor)和任意數量的附加參數(...args)。createInstance 函數通過使用傳入的參數調用構造函數,創建并返回指定類的實例。
// 定義一個泛型工廠函數
function createInstance<T>(constructor:new (...args: any[]) => T, ...args: any[]): T {return new constructor(...args);
}// 示例類
class Product {constructor(public name: string, // 產品名稱public price: number) { } // 產品價格
}class Person {constructor(public name: string, // 人名public age: number) { } // 年齡
}// 使用工廠函數創建實例
const laptop = createInstance(Product, 'Laptop', 999);
console.log(laptop);const user = createInstance(Person, 'TypeScript', 30);
console.log(user);
輸出:
Product { name: 'Laptop', price: 999 }
Person { name: 'TypeScript', age: 30 }
2. 例二
在這個例子中,我們定義了一個基類 Animal,包含一個 name 屬性和一個 makeSound 方法。Dog 類繼承自 Animal,新增了 breed 屬性并重寫了 makeSound 方法。我們創建了一個泛型函數 printAnimalInfo,該函數接受一個類型為 T 的參數,且 T 必須繼承自 Animal 類。這意味著它可以接受 Animal 類或其子類的實例。然后我們創建了 Animal 和 Dog 的實例,并分別調用 printAnimalInfo 函數。
// 定義基類 Animal
class Animal {// 構造函數,初始化公開屬性 nameconstructor(public name: string) { }// 返回動物發出的通用聲音makeSound(): string {return "Some generic animal sound";}
}// 定義繼承自 Animal 的子類 Dog
class Dog extends Animal {// 構造函數,接收 name 和 breed 兩個參數constructor(name: string, public breed: string) {super(name); // 調用父類構造函數,初始化 name}// 重寫父類的 makeSound 方法,返回狗叫聲makeSound(): string {return "Woof! Woof!";}
}// 定義泛型函數,要求類型 T 繼承自 Animal
function printAnimalInfo<T extends Animal>(animal: T): void {// 打印動物的名字console.log(`Name: ${animal.name}`);// 打印動物的叫聲(調用 makeSound 方法)console.log(`Sound: ${animal.makeSound()}`);}// 創建 Animal 類的實例
const genericAnimal = new Animal("Generic Animal");
// 創建 Dog 類的實例
const myDog = new Dog("Buddy", "Golden Retriever");// 使用泛型函數,分別傳入 Animal 和 Dog 實例
printAnimalInfo(genericAnimal);
printAnimalInfo(myDog);
輸出:
Name: Generic Animal
Sound: Some generic animal sound
Name: Buddy
Sound: Woof! Woof!
六、泛型對象類型
TypeScript 泛型對象類型允許你為對象創建靈活且可復用的類型定義。這些泛型類型可以處理不同結構的對象,同時保證類型安全,確保代碼既健壯又具有適應性。它們特別適合用于創建能夠處理多種對象結構的函數或類,同時保持類型的正確性。
(一) 語法
type MyGenericObject<T> = {key: string;value: T;
};
(二) 用法示例
1. 鍵值對
在此示例中,我們創建了一個泛型對象類型來表示鍵值對。類型參數 T 表示值的類型,使得可以使用不同的數據類型。
type KeyValuePair<T> = {key: string;value: T;
};const stringPair: KeyValuePair<string> = { key: 'name', value: 'John' };
const numberPair: KeyValuePair<number> = { key: 'age', value: 30 };console.log(stringPair);
console.log(numberPair);
輸出:
{ key: 'name', value: 'John' }
{ key: 'age', value: 30 }
2. 封裝數據屬性
在此示例中,我們定義了一個泛型對象類型,用于封裝指定類型 T 的數據屬性。這個例子展示了泛型對象類型如何存儲和訪問不同的數據類型。
type DataContainer<T> = {data: T;
};const numericData: DataContainer<number> = { data: 25 };
const stringData: DataContainer<string> = { data: 'TypeScript' };console.log(numericData.data);
console.log(stringData.data);
輸出:
25
TypeScript
(三) 總結
TypeScript 的泛型對象類型提供了一種強大的方式來創建靈活、可復用且類型安全的對象定義。通過利用這些類型,你可以構建更健壯且適應性更強的代碼,確保函數和類能夠處理不同的對象結構,同時保持嚴格的類型正確性。這種方法提升了代碼的可維護性,降低了出錯的風險,成為 TypeScript 開發者工具箱中不可或缺的重要工具。