理解 TypeScript 泛型

什麼是泛型(Generics)

定義:泛型容許咱們延遲編寫類或方法中的編程元素的數據類型的規範,直到實際在程序中使用它的時候。編程

解釋定義

請看以下代碼:數組

function identity<T>(arg: T) {
  return arg;
}

function main() {
  const result = identity<number>(1);
}

複製代碼

咱們定義了一個泛型方法identity,定義了一個main方法,調用identity方法。bash

定義identity的時候,咱們並不知道參數arg是個什麼類型,T就是一個佔位符,告訴咱們arg是T類型的。編輯器

在調用identity的時候,才告訴它我要給你傳遞一個number類型的,而後傳遞真正的arg參數的值,是1。ide

這就是泛型定義中所說的,延遲編寫方法(==identity==)中的編程元素(==arg參數==)的類型(==不寫number,寫T==),直到實際在程序中(==main方法==)使用它的時候。函數

類型參數化

上例中,將類型(number)變成了函數identity的一個參數,在調用的時候才傳給函數identity,就叫作類型參數化。學習

泛型有什麼好處

看完上面的解釋,你多半會疑惑,這麼寫有什麼好處啊,之前那種不也挺好嗎,以下:ui

function identity(arg: number) {
    return arg;
}

function main() {
  const result = identity(1);
}
複製代碼

這不也實現了相同的邏輯嗎?何須延遲到調用的時候,才肯定參數的類型呢?this

答案是 ==泛型可讓不一樣類型的數據,複用一樣的方法或類。== 若是方法或類在定義的時候,就固定參數的類型,那麼此方法或類只能處理這一種類型的數據。但等調用的時候才肯定參數類型,就可讓多種參數類型的數據,複用同一個方法或類。spa

這裏咱們分析一個稍微複雜的例子,內置對象Array的聲明:

interface Array<T> {
    length: number;
    
    toString(): string;
    
    pop(): T | undefined;
    reverse(): T[];
    sort(compareFn?: (a: T, b: T) => number): this;
    
    ...
}
複製代碼

這裏只是列出小部分Array的聲明。我仍是按泛型的定義解釋一邊,在咱們編寫Arry的時候,延遲定義編程元素(這裏指在Array類內能夠使用的T)的數據類型。

使用:

function main() {
  const nums = new Array<number>(1, 2, 3);
  nums.push(4);

  const popNum = nums.pop();
}

複製代碼

直到實際在main函數中使用它的時候。咱們才告訴Array,咱們要一個數字類型的數組。

咱們還能夠

function main() {
  const dates = new Array<Date>();
  dates.push(new Date('2019-7-8'));
  dates.push(new Date('2019-7-9'));
  dates.push(new Date('2019-7-10'));

  const reverseDates = dates.reverse();
}
複製代碼

注意的問題的關鍵沒,關鍵在於pop,reverse這樣的方法的內在邏輯,是不關心被操縱數據的類型的,因此能夠實現對於不一樣類型的的複用。

因此,Array類就實現類對於不一樣類型的數據列表的相關操做的複用。

約束依舊存在

若是您是由Javascript爲基礎,學習的TypeScript,或許會以爲上面說的有些莫名其妙,TypeScript搞這個泛型幹啥,JS對此徹底沒有限制啊,沒有類型,能夠隨意傳參啊,以下:

function mainAboutNumber() {
    var nums = new Array(1, 2, 3);
    nums.push(4);
    var popNum = nums.pop();
}

function mainAboutDate() {
    var dates = new Array();
    dates.push(new Date('2019-7-8'));
    dates.push(new Date('2019-7-9'));
    dates.push(new Date('2019-7-10'));
    var reverseDates = dates.reverse();
}
複製代碼

確實如此,若是沒有類型,就沒有泛型這一說了,泛型是對類型的一種放寬政策,但約束仍舊存在。若是是用JS,這樣的代碼在編寫時不會出錯:

function mainAboutDate() {
    var dates = new Array();
    dates.push(new Date('2019-7-8'));
    dates.push('我不是Date類型');
    dates.push(new Date('2019-7-10'));
    var reverseDates = dates.reverse();
}
複製代碼

但若是你的業務中,dates是表明一個日期列表,顯然仍是TS更好:

image

會在咱們編程期間及時報錯,泛型的約束力依舊存在,當給定參數的類型後,咱們就有了一個日期類型的數組了。

泛型的其餘約束力

除了複用這條優勢,泛型還能夠幫助咱們實現入參和返回值類型一致的校驗,以下:

function identity<T>(arg: T): T {
    return arg;
}
複製代碼

如方法後面的冒號+T,就是限定返回值的類型也是T,要和參數arg的T保持一致,當咱們使用它的時候:

image

將鼠標浮動到num變量上,發現它是個number類型,這是由於咱們將T設置成了numnber。經過類型推斷,num就成了number類型,後面就能夠經過編輯器的智能提示,看到number類型的全部方法了,方便咱們的編程。

泛型變量

在定義方法或類的時候,咱們不光能夠使用T,還能夠爲T增長更多修飾,使其變成一種有更多信息的泛型變量。

以下是Object.freeze方法的簽名。

Object.freeze() 方法能夠凍結一個對象。一個被凍結的對象不再能被修改;凍結了一個對象則不能向這個對象添加新的屬性,不能刪除已有屬性,不能修改該對象已有屬性的可枚舉性、可配置性、可寫性,以及不能修改已有屬性的值。此外,凍結一個對象後該對象的原型也不能被修改。freeze() 返回和傳入的參數相同的對象。 --- MDN定義

interface ObjectConstructor {
    freeze<T>(a: T[]): ReadonlyArray<T>;
    
    ...
}
複製代碼

咱們來看freeze(a: T[]): ReadonlyArray簽名。 這個簽名的意思是,若是你傳遞的是個數組(a:T[]),返回值會是一個ReadonlyArray。以下是使用時的代碼:

function main() {
  const nums = new Array<number>(1, 2, 3); 
  const freezeNums = Object.freeze(nums);
}
複製代碼

咱們就獲得了一個readonly的數字數組。這裏的T[]和ReadonlyArray。利用T進一步描述freeze方法的參數和返回值。若是單單用T,顯然信息量不夠多。

另外假設讓咱們來實現freeze方法。那內部咱們就能夠調用a.length這樣的代碼,由於簽名已經明確告訴咱們,入參a是某種數組類型。

泛型約束

泛型約束又是什麼?咱們在看freeze方法的另外一個重載:

interface ObjectConstructor {
    freeze<T>(a: T[]): ReadonlyArray<T>;
    freeze<T extends Function>(f: T): T;
     
    ...
}
複製代碼

不一樣於泛型變量,這回咱們約束尖括號<>裏的T,freeze(f: T): T;的意思是,若是傳遞給freeze的是個方法,那返回值仍是個方法。

再看一個例子:

interface SpecialType {
  specialProperty: string;
}

function identity<T extends SpecialType>(arg: T): T {
  console.log(arg.specialProperty);
  return arg;
}
複製代碼

這個例子中,咱們讓T繼承SpecialType,這樣咱們在identity方法內部,就能夠使用SpecialType規定的屬性了。

泛型約束能夠說增長了泛型的複用能力,可讓某些含有相同屬性的數據,複用一樣須要這些屬性的方法。

再多分析一個包含泛型的簽名

interface Array<T> {
    ...
    
    map<U>(callbackfn: (value: T, index: number, array: T[]) => U, thisArg?: any): U[];
    
    
    ...
}
複製代碼

咱們來分析一下Array類中的map函數,也是咱們常常使用的函數。

map函數簽名

第一個參數是個回調函數,回調函數的第一個參數value是T類型,第二個參數index是數字類型,第三個參數是T類型的列表。回調函數的返回值是一個U類型的數據。map函數的返回值是U類型的列表,是個全新類型的列表。

結束語

學習泛型,可讓咱們很好的理解第三方庫的TS聲明。也可讓咱們本身寫出優秀的TypeScript代碼。

相關文章
相關標籤/搜索