TypeScript 泛型入門

本文作者爲 360 奇舞團前端開發工程師

一、什麼是泛型

泛型,我們光從字面上來推斷,泛,寬泛,廣泛,型,型號,類型。所以我們可以先認爲,泛型就是給我們的代碼增加一種相對寬泛的類型約束。在 TypeScript 中,我們定義一個變量,我們可以賦予其一種確定的類型。使得我們的代碼具有更好的維護性,但是在增強代碼的可維護性同時,我們又要考慮代碼應該具有一定的靈活性。使得在未來,代碼也能被複用。於是泛型就在這個背景下出現了。

二、泛型函數

const printFun = (value) ={
    console.log(value);
    return value;
};

console.log(printFun("hello world"));

我們在上面代碼中創建了一個函數,作用很簡單,就是打印我們傳入的參數並返回該值。這時我們沒有添加任何類型約束。我麼可以傳入一切類型,但是我們現在希望,該函數只能打印我們傳入的類型。而且返回值的類型一定要和傳入的參數類型一致,所以我們就要給代碼增加一定約束,就像下面這樣。

const printFun = (value: string)string ={
  console.log(value);
  return value;
};

printFun("hello world");

現在我們的函數就只能接收字符串類型的變量了,並將其返回。

在未來的某一天,我們需要打印一個數字,我們調用這個函數的時候,就會報錯,並提示我們需要傳入字符串類型,前面這段代碼雖然有了一定的約束性,但是也丟失了靈活性,我們當然可以通過重新定義一個函數,來解決這個問題,但是,我們如果使用泛型來解決這個問題,就顯得更加合適。

const printFun = <T>(value: T)T ={
  console.log(value);
  return value;
};

printFun(233);

我們在代碼中引入了一對尖括號,並傳入了一個 T,字母 T 代表一種類型,一種沒有確定的類型,我們可以把這個 T 看作和函數所接收的參數 value 一樣,是一個佔位符,我們可以用自己喜歡的字母,單詞來替換掉 T,在我們沒有給函數傳參之前,我們只通過這個函數,至少就能知道一點,函數的入參和出參,在類型上是一致的,這就對我們的代碼形成了約束,但是這種約束和我們具體定義某一種類型不同,我們定義函數時,也並不知道,T 具體會是什麼類型。我們可以把這種寫法和函數傳參進行對比,聲明函數時,我們也不知道 value 的值會是什麼。但是在給函數傳參的時候,我們 value 的值就確定了,此時 T 的類型也被確定了,推斷出 T 是 number 類型。同理,我們如果傳入一個字符串的話,T 就會被確定爲 string 類型,這樣我們的代碼在有了更好的靈活性的同時,也具有了一定的約束。

三、泛型接口

interface 是我們在使用 TS 的時候,最常用的關鍵字。

interface Person {
  name: string;
  age: number;
}
const me: Person = {
  name: "sunny",
  age: 18,
};

我們在前面的代碼中用聲明接口限制了一個對象,我們也可以通過泛型對我們的接口進行改造。

這時候我們通過定義 J,K 兩個佔位符,使得我們的接口具備了泛型,更加靈活。但是我們發現泛型接口和泛型函數不一樣,我們像前面圖片中這樣使用,有一個地方報錯了,提示我們需要傳入類型參數,而不能依賴自動的類型推斷,來確實對象屬性的類型,其實這也是可以理解,因爲我們對一個對象的限制,主要就是限制對象的屬性類型,如果我們不確定類型,那我們傳入的一切類型都是有效的了。就起不到類型的限制,而前面我們使用泛型函數,雖然沒有顯示聲明類型,但是我們達到了對函數入參和出參進行了統一。

interface Person<J, K> {
  name: J;
  age: K;
}
const me: Person<string, number> = {
  name: "sunny",
  age: 18,
};

這次我們在使用泛型接口時,對其傳入了兩個確定的類型。J,K 的類型也因此確定了。其實在學習泛型之前,大家或許就已經使用過泛型了,比如

const arr: Array<number> = [1, 2, 3];

這是 TS 中定義一個數組的一種寫法。其實就是定義了一個數組對象,並限制了數組中每個元素的類型爲 number。

四、泛型約束

我們使用泛型也是爲了對我們的代碼進行類型的約束, 但是泛型我們知道是一種沒有確定的類型,我們通過下面的代碼來感受一下。

我們給函數傳入了一個對象,在函數中想要讀取其所具有的屬性值,但是編輯器卻提示我們類型 T 上沒有 age 和 name 屬性。因爲泛型本身就不是某一種具體的類型,所以靜態的類型檢查,自然無法判斷出,其是否具有相應的類型。我們可以對函數進行一些改造,像下面這樣。

interface Person<J, K> {
  name: J;
  age: K;
}
const printFun = <T extends Person<string, number>, S>(
  person: T,
  msg: S
)S ={
  console.log(person.age);
  console.log(person.name);
  return msg;
};
printFun({ name: "sunny", age: 18 }"success");

這次我們使用了 extends 關鍵字,和接口泛型,對類型 T 進行了一些擴展,使得其具備了我們所要求的屬性。

五、小結

通過前面幾節的介紹,我們對泛型有了初步的瞭解,知道泛型出現的背景和意義,能將泛型初步的使用於我們的項目之中。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/MPBNIz8dbDtVTYA2Z_0UAw