前端深刻理解Typescript泛型概念

整理一些我對於泛型的理解。

首先介紹一下泛性的概念

泛型程序設計(generic programming)是程序設計語言的一種風格或範式。泛型容許程序員在強類型程序設計語言中編寫代碼時使用一些之後才指定的類型,在實例化時做爲參數指明這些類型。程序員

泛型是指在定義函數,接口或者類的時候,不預先定義好具體的類型,而在使用的時候在指定類型的一種特性。數組

先舉一個簡單的例子

假設咱們定義一個函數,它能夠接收一個number類型作爲參數,而且返回一個number類型。安全

function genericDemo(data: number): number {
    return data;
}
複製代碼

按照以上的寫法是沒有問題的,可是若是咱們要接受一個string並返回一個string呢?若是邏輯同樣還要在寫一遍嗎?就像下面這樣。bash

function genericDemo(data: string): string {
    
    return data;
}
複製代碼

這顯然代碼是很冗餘的,咱們還有不使用any的寫法嗎?答案是顯然易見的,可使用範型的寫法,就像下面這樣。markdown

function genericDemo<T>(data: T):T {
    return data;
}
複製代碼

咱們在函數名稱genericDemo後面聲明瞭範型變量<T>,他用於捕獲調用該函數時傳入的參數類型(例如:number),以後咱們就可使用這個類型。 以後咱們再次使用了T當作返回值類型。如今咱們能夠知道參數類型與返回值類型是相同的了。這容許咱們跟蹤函數裏使用的類型的信息。ide

多個類型參數

咱們在定義範型的時候,也能夠一次定義多個類型參數,像下面這樣。函數

function swap<T, U>(tuple: [T, U]):[U, T] {
    return [tuple[1], tuple[0]];
}
複製代碼

泛型接口

咱們先定義一個範型接口Identities,而後定義一個函數identities()來使用這個範型接口學習

interface Identities<T, U> {
    id1: T;
    id2: U;
}
複製代碼

我在這裏使用TU做爲咱們的類型變量來演示任何字母(或有效的字母數字名稱的組合)都是有效的類型—除了常規用途以外,您對它們的調用沒有任何意義。spa

咱們如今能夠將這個接口應用爲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

總結

以上內容是我這段時間學習ts泛型的一些總結,但願能夠對讀到的你有所幫助。 喜歡的同窗也能夠關注。每週都會持續更新不一樣的內容。

相關文章
相關標籤/搜索