前端深入理解Typescript泛型概念
首先介紹一下泛性的概念
泛型程式設計(generic programming)是程式設計語言的一種風格或正規化。泛型允許程式設計師在強型別程式設計語言中編寫程式碼時使用一些以後才指定的型別,在例項化時作為引數指明這些型別。
泛型是指在定義函式,介面或者類的時候,不預先定義好具體的型別,而在使用的時候在指定型別的一種特性。
先舉一個簡單的例子
假設我們定義一個函式,它可以接收一個number型別做為引數,並且返回一個number型別。
function genericDemo(data: number): number { return data; }
按照以上的寫法是沒有問題的,但是如果我們要接受一個string並返回一個string呢?如果邏輯一樣還要在寫一遍嗎?就像下面這樣。
function genericDemo(data: string): string { return data; }
這顯然程式碼是很冗餘的,我們還有不使用any的寫法嗎?答案是顯然易見的,可以使用範型的寫法,就像下面這樣。
function genericDemo<T>(data: T):T { return data; }
我們在函式名稱genericDemo後面聲明瞭範型變數<T>,他用於捕獲呼叫該函式時傳入的引數型別(例如:number),之後我們就可以使用這個型別。 之後我們再次使用了T當做返回值型別。現在我們可以知道引數型別與返回值型別是相同的了。這允許我們跟蹤函式裡使用的型別的資訊。
多個型別引數
我們在定義範型的時候,也可以一次定義多個型別引數,像下面這樣。
function swap<T,U>(tuple: [T,U]):[U,T] { return [tuple[1],tuple[0]]; }
泛型介面
我們先定義一個範型介面Identities,然後定義一個函式identities()來使用這個範型介面
interface Identities<T,U> { id1: T; id2: U; }
我在這裡使用T和U作為我們的型別變數來演示任何字母(或有效的字母數字名稱的組合)都是有效的型別—除了常規用途之外,您對它們的呼叫沒有任何意義。
我們現在可以將這個介面應用為identity()的返回型別,修改我們的返回型別以符合它。我們還可以console.log這些引數和它們的型別,以便進一步說明:
function identities<T,U> (arg1: T,arg2: U): Identities<T,U> { console.log(arg1 + ": " + typeof (arg1)); console.log(arg2 + ": " + typeof (arg2)); let identities: Identities<T,U> = { id1: arg1,id2: arg2 }; return identities; }
我們現在對identity()所做的是將型別T和U傳遞到函式和identity介面中,從而允許我們定義與引數型別相關的返回型別。
範型變數
使用泛型建立像identity這樣的泛型函式時,編譯器要求你在函式體必須正確的使用這個通用的型別。 換句話說,你必須把這些引數當做是任意或所有型別。
我們先看下之前例子
function genericDemo<T>(data: T):T { return data; }
如果我們想同時打印出data的長度。 我們很可能會這樣做
function genericDemo<T>(data: T):T { console.log(data.length); // Error: T doesn't have .length return data; }
如果這麼做,編譯器會報錯說我們使用了data的.length屬性,但是沒有地方指明data具有這個屬性。 記住,這些型別變數代表的是任意型別,所以使用這個函式的人可能傳入的是個數字,而數字是沒有.length屬性的。
現在假設我們想操作T型別的陣列而不直接是T。由於我們操作的是陣列,所以.length屬性是應該存在的。 我們可以像建立其它陣列一樣建立這個陣列:
function genericDemo<T>(data: Array<T>):Array<T> { console.log(data.length); return data; }
範型類
我們還可以在類屬性和方法的意義上使類泛型。泛型類確保在整個類中一致地使用指定的資料型別。例如下面這種在React Typescript專案中的寫法。
interface Props { className?: string; ... } interface State { submitted?: bool; ... } class MyComponent extends React.Component<Props,State> { ... }
我們在這裡使用與React元件一起使用的泛型,以確保元件的props和state是型別安全的。
泛型約束
我們先看一個常見的需求,我們要設計一個函式,這個函式接受兩個引數,一個引數為物件,另一個引數為物件上的屬性,我們通過這兩個引數返回這個屬性的值,比如:
function getValue(obj: object,key: string){ return obj[key] // error }
我們會得到一段報錯,這是新手 TypeScript 開發者常常犯的錯誤,編譯器告訴我們,引數 obj 實際上是 {},因此後面的 key 是無法在上面取到任何值的。
因為我們給引數 obj 定義的型別就是 object,在預設情況下它只能是 {},但是我們接受的物件是各種各樣的,我們需要一個泛型來表示傳入的物件型別,比如T extends object:
function getValue<T extends object>(obj: T,key: string) { return obj[key] // error }
這依然解決不了問題,因為我們第二個引數 key 是不是存在於 obj 上是無法確定的,因此我們需要對這個 key 也進行約束,我們把它約束為只存在於 obj 屬性的型別,這個時候需要藉助到後面我們會進行學習的索引型別進行實現 <U extends keyof T>,我們用索引型別 keyof T 把傳入的物件的屬性型別取出生成一個聯合型別,這裡的泛型 U 被約束在這個聯合型別中,這樣一來函式就被完整定義了:
function getValue<T extends object,U extends keyof T>(obj: T,key: U) { return obj[key] // ok }
另外提一個多重泛型約束的寫法,可以當作拓展:
interface firstInterface { first(): number } interface secondInterface { second(): string } class Demo<T extends firstInterface & secondInterface >{ ... }
在泛型裡使用類型別
在TypeScript使用泛型建立工廠函式時,需要引用建構函式的類型別。比如:
function create<T>(type: {new(): T; }): T { return new type(); }
引數type的型別{new(): T}就表示此泛型T是可被構造的,在被例項化後的型別是泛型 T。
總結
到此這篇關於前端深入理解Typescript泛型概念的文章就介紹到這了,更多相關Typescript 泛型內容請搜尋我們以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援我們!