面試官:說說你對 TypeScript 中泛型的理解?應用場景?
本文轉載自微信公眾號「JS每日一題」,作者灰灰。轉載本文請聯系JS每日一題公眾號。
一、是什么
泛型程序設計(generic programming)是程序設計語言的一種風格或范式
泛型允許我們在強類型程序設計語言中編寫代碼時使用一些以后才指定的類型,在實例化時作為參數指明這些類型 在typescript中,定義函數,接口或者類的時候,不預先定義好具體的類型,而在使用的時候在指定類型的一種特性
假設我們用一個函數,它可接受一個 number 參數并返回一個number 參數,如下寫法:
- function returnItem (para: number): number {
- return para
- }
如果我們打算接受一個 string 類型,然后再返回 string類型,則如下寫法:
- function returnItem (para: string): string {
- return para
- }
上述兩種編寫方式,存在一個最明顯的問題在于,代碼重復度比較高
雖然可以使用 any類型去替代,但這也并不是很好的方案,因為我們的目的是接收什么類型的參數返回什么類型的參數,即在運行時傳入參數我們才能確定類型
這種情況就可以使用泛型,如下所示:
- function returnItem<T>(para: T): T {
- return para
- }
可以看到,泛型給予開發者創造靈活、可重用代碼的能力
二、使用方式
泛型通過<>的形式進行表述,可以聲明:
- 函數
- 接口
- 類
函數聲明
聲明函數的形式如下:
- function returnItem<T>(para: T): T {
- return para
- }
定義泛型的時候,可以一次定義「多個類型參數」,比如我們可以同時定義泛型 T 和 泛型 U:
- function swap<T, U>(tuple: [T, U]): [U, T] {
- return [tuple[1], tuple[0]];
- }
- swap([7, 'seven']); // ['seven', 7]
接口聲明
聲明接口的形式如下:
- interface ReturnItemFn<T> {
- (para: T): T
- }
那么當我們想傳入一個number作為參數的時候,就可以這樣聲明函數:
- const returnItem: ReturnItemFn<number> = para => para
類聲明
使用泛型聲明類的時候,既可以作用于類本身,也可以作用于類的成員函數
下面簡單實現一個元素同類型的棧結構,如下所示:
- class Stack<T> {
- private arr: T[] = []
- public push(item: T) {
- this.arr.push(item)
- }
- public pop() {
- this.arr.pop()
- }
- }
使用方式如下:
- const stack = new Stacn()
如果上述只能傳遞 string 和 number 類型,這時候就可以使用
除了上述的形式,泛型更高級的使用如下:
例如要設計一個函數,這個函數接受兩個參數,一個參數為對象,另一個參數為對象上的屬性,我們通過這兩個參數返回這個屬性的值
這時候就設計到泛型的索引類型和約束類型共同實現
索引類型、約束類型
索引類型 keyof T 把傳入的對象的屬性類型取出生成一個聯合類型,這里的泛型 U 被約束在這個聯合類型中,如下所示:
- function getValue<T extends object, U extends keyof T>(obj: T, key: U) {
- return obj[key] // ok
- }
上述為什么需要使用泛型約束,而不是直接定義第一個參數為 object類型,是因為默認情況 object 指的是{},而我們接收的對象是各種各樣的,一個泛型來表示傳入的對象類型,比如 T extends object
使用如下圖所示:
多類型約束
例如如下需要實現兩個接口的類型約束:
- interface FirstInterface {
- doSomething(): number
- }
- interface SecondInterface {
- doSomethingElse(): string
- }
可以創建一個接口繼承上述兩個接口,如下:
- interface ChildInterface extends FirstInterface, SecondInterface {
- }
正確使用如下:
- class Demo<T extends ChildInterface> {
- private genericProperty: T
- constructor(genericProperty: T) {
- this.genericProperty = genericProperty
- }
- useT() {
- this.genericProperty.doSomething()
- this.genericProperty.doSomethingElse()
- }
- }
通過泛型約束就可以達到多類型約束的目的
三、應用場景
通過上面初步的了解,后述在編寫 typescript 的時候,定義函數,接口或者類的時候,不預先定義好具體的類型,而在使用的時候在指定類型的一種特性的時候,這種情況下就可以使用泛型
靈活的使用泛型定義類型,是掌握typescript 必經之路
參考文獻
https://www.tslang.cn/docs/handbook/generics.html