十分鐘教你理解TypeScript中的泛型

来源:https://www.cnblogs.com/powertoolsteam/archive/2019/07/11/11170031.html
-Advertisement-
Play Games

本文介紹TypeScript中泛型的概念,以簡單直白的方式,向那些不瞭解此概念,但渴望在工作中使用它的開發者,提供入門指導。 ...


轉載請註明出處:葡萄城官網,葡萄城為開發者提供專業的開發工具、解決方案和服務,賦能開發者。
原文出處:https://blog.bitsrc.io/understanding-generics-in-typescript-1c041dc37569

你將在本文中學到什麼

本文介紹TypeScript中泛型(Generics)的概念和用法,它為什麼重要,及其使用場景。我們會以一些清晰的例子,介紹其語法,類型和如何構建參數。你可以在你的集成開發環境中跟著實踐。

準備工作

要從本文中跟著學習的話,你需要在電腦上準備以下東西:

  • 安裝Node.js:你可以運行命令行檢查Node是否安裝好了。
node -v
  • 安裝Node Package Manager: 通常安裝Node時,它會順帶安裝好所需版本的NPM。
  • 安裝TypeScript:如果你安裝好了Node Package Manager,你可以用以下命令在本機的全局環境安裝TypeScript。
  • npm install -g typescript
    集成開發環境:本文將使用微軟團隊開發的Visual Studio Code。可以在這裡下載。進入其下載的目錄,並按照提示進行安裝。記得選擇“添加打開代碼”(Add open with code)選項,這樣你就可以在本機從任何位置輕鬆打開VS Code了。

本文是寫給各層次的TypeScript開發人員的,包括但並不只是初學者。 這裡給出了設置工作環境的步驟,是為了照顧那些TypeScript和Visual Studio Code的新手們。

TypeScript里的泛型是個啥

在TypeScript中,泛型是一種創建可復用代碼組件的工具。這種組件不只能被一種類型使用,而是能被多種類型復用。類似於參數的作用,泛型是一種用以增強類(classes)、類型(types)和介面(interfaces)能力的非常可靠的手段。這樣,我們開發者,就可以輕鬆地將那些可復用的代碼組件,適用於各種輸入。然而,不要把TypeScript中的泛型錯當成any類型來使用——你會在後面看到這兩者的不同。

類似C#和Java這種語言,在它們的工具箱里,泛型是創建可復用代碼組件的主要手段之一。即,用於創建一個適用於多種類型的代碼組件。這允許用戶以他們自己的類使用該泛型組件。

在VS Code中配置TypeScript

在電腦中創建一個新文件夾,然後使用VS Code 打開它(如果你跟著從頭開始操作,那你已經安裝好了)。

在VS Code中,創建一個app.ts文件。我的TypeScript代碼都會放在這裡面。

把下麵打日誌的代碼拷貝到編輯器中:

console.log("hello TypeScript");

按下F5鍵,你會看到一個像這樣的launch.json文件:

{
  // Use IntelliSense to learn about possible attributes.
  // Hover to view descriptions of existing attributes.
  // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
  "version": "0.2.0",
  "configurations": [
    {
      "type": "node",
      "request": "launch",
      "name": "TypeScript",
      "program": "${workspaceFolder}\\app.ts",
      "outFiles": [
        "${workspaceFolder}/**/*.js"
      ]
    }
  ]
}

裡面的name欄位的值,本來是Launch Program,我把它改成了TypeScript。你可以把它改成其他值。

點擊Terminal Tab,選擇Run Tasks,再選擇一個Task Runner:"TypeScript Watch Mode",然後會彈出一個tasks.json文件,把它改成下麵像這樣:

 {
  // See https://go.microsoft.com/fwlink/?LinkId=733558
  // for the documentation about the tasks.json format
  "version": "2.0.0",
  "tasks": [
  {
   "label": "echo",
   "type": "shell",
   "command": "tsc",
   "args": ["-w", "-p","."],
   "problemMatcher": [
    "$tsc-watch"
    ],
   "isBackground": true
   }
  ]
 }

app.ts所在的目錄,創建另一個文件tsconfig.json。把下麵的代碼拷貝進去:

{
  "compilerOptions": {
    "sourceMap": true
  }
}

這樣,Task Runner就可以把TypeScript編譯成JavaScript,並且可監聽到文件的變化,實時編譯。

再次點擊Ternimal標簽,選擇Run Build Task,再選擇tsc: watch - tsconfig.json,可以看到終端出現的信息:

[21:41:31] Starting compilation in watch mode…

你可以使用VS Code的調試功能編譯TypeScript文件。  

設置好了開發環境,你就可以著手處理TypeScript泛型概念相關的問題了。

找到問題

TypeScript中不建議使用any類型,原因有幾點,你可以在本文看到。其中一個原因,就是調試時缺乏完整的信息。而選擇VS Code作為開發工具的一個很好的理由,就是它帶來的基於這些信息的智能感知。

如果你有一個類,存儲著一個集合。有方法向該集合里添加東西,也有方法通過索引獲取集合里的東西。像這樣:

class Collection {
  private _things: string[];
  constructor() {
    this._things = [];
  }
  add(something: string) {
    this._things.push(something);
  }
  get(index: number): string {
    return this._things[index];
  }
}

你可以很快辨識出,此集合被顯示定義為一個string類型的集合,顯然是不能在其中使用number的。如果想要處理number的話,可以創建一個接受number而不是string的集合。著是一個不錯的選擇,但有一個很大的缺點——代碼重覆。代碼重覆,最終會導致編寫和調試代碼的時間增多,並且降低記憶體的使用效率。

另一個選擇,是使用any類型代替string類型定義剛纔的類,像下麵這樣:

class Collection {
  private _things: any[];
  constructor() {
    this._things = [];
  }
  add(something: any) {
    this._things.push(something);
  }
  get(index: number): any {
    return this._things[index];
  }
}

此時,該集合支持你給出的任何類型。如果你創建像這樣的邏輯構建此集合的話:

let Stringss = new Collection();
Stringss.add("hello");
Stringss.add("world");

這添加了字元串"hello"和"world"到集合中,你可以打出像length這樣的屬性,返回任意一個集合元素的長度。  

console.log(Stringss.get(0).length);

字元串"hello"有五個字元,運行TypeScript代碼,你可以在調試模式下看到它。  

請註意,當你滑鼠懸停在length屬性上時,VS Code的智能感知沒有提供任何信息,因為它不知道你選擇使用的確切類型。當你像下麵這樣,把其中一個添加的元素修改為其他類型時,比如number,這種不能被智能感知到的情況會體現得更加明顯:

let Strings = new Collection();
Strings.add(001);
Strings.add("world");
console.log(Strings.get(0).length);

你打出一個undefined的結果,仍然沒有什麼有用信息。如果你更進一步,決定列印string的子字元串——它會報運行時錯誤,但不指不出任何具體的內容,更重要的是,編譯器沒有給出任何類型不匹配的編譯時錯誤。  

console.log(Stringss.get(0).substr(0,1));

這僅僅是使用any類型定義該集合的一種後果罷了。

理解中心思想

剛纔使用any類型導致的問題,可以用TypeScript中的泛型來解決。其中心思想是類型安全。使用泛型,你可以用一種編譯器能理解的,並且合乎我們判斷的方式,指定類、類型和介面的實例。正如在其他強類型語言中的情況一樣,用這種方法,就可以在編譯時發現你的類型錯誤,從而保證了類型安全。

泛型的語法像這樣:

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

你可以在之前創建的集合中使用泛型,用尖括弧括起來。  

class Collection<T> {
  private _things: T[];
  constructor() {
    this._things = [];
  }
  add(something: T): void {
    this._things.push(something);
  }
  get(index: number): T {
    return this._things[index];
  }
}
let Stringss = new Collection<String>();
Stringss.add(001);
Stringss.add("world");
console.log(Stringss.get(0).substr(0, 1));

如果將帶有尖括弧的新邏輯複製到代碼編輯器中,你會立即註意到"001"下的波浪線。這是因為,TypeScript現在可以從指定的泛型類型推斷出001不是字元串。在T出現的地方,就可以使用string類型,這就實現了類型安全。本質上,這個集合的輸出可以是任何類型,但你指明瞭它應該是string類型,所以編譯器推斷它就是string類型。這裡使用的泛型聲明是在類級別,它也可以在其他級別定義,如靜態方法級別和實例方法級別,你稍後會看到。

使用泛型

你可以在泛型聲明中,包含多個類型參數,它們只需要用逗號分隔,像這樣:

class Collection<T, K> {
  private _things: K[];
  constructor() {
    this._things = [];
  }
  add(something: K): void {
    this._things.push(something);
  }
  get(index: number): T {
    console.log(index);
  }
}

聲明時,類型參數也可以在函數中顯式使用,比如:

 

class Collection {
  private _things: any[];
  constructor() {
    this._things = [];
  }
  add<A>(something: A): void {
    this._things.push(something);
  }
  get<B>(index: number): B {
    return this._things[index];
  }
}

因此,當你要創建一個新的集合時,在方法級別聲明的泛型,現在也會在方法調用級別中被指示,像這樣:  

let Stringss = new Collection();
Stringss.add<string>("hello");
Stringss.add("world");

你還可註意到,在滑鼠懸停時,VS Code智能感知能夠推斷出第二個add函數調用仍然是string類型。

泛型聲明同樣適用於靜態方法:

static add<A>(something: A): void {
  _things.push(something);
}

雖然初始化靜態方法時,可使用泛型類型,但是,對初始化靜態屬性則不能。

泛型約束

現在,你已經對泛型有比較好的認識,是時候提到泛型的核心缺點及其實用的解決方案了。使用泛型,許多屬性的類型都能被TypeScript推斷出來,然而,在某些TypeScript不能做出準確推斷的地方,它不會做任何假設。為了類型安全,你需要將這些要求或者約束定義為介面,併在泛型初始化中繼承它們。

如果你有這樣一個非常簡單的函數:

function printName<T>(arg: T) {
  console.log(arg.length);
  return arg;
}
printName(3);

因為TypeScript無法推斷出arg參數是什麼類型,不能證明所有類型都具有length屬性,因此不能假設它是一個字元串(具有length屬性)。所以,你會在length屬性下看到一條波浪線。如前所述,你需要創建一個介面,讓泛型的初始化可以繼承它,以便編譯器不再報警。  

interface NameArgs {
  length: number;
}

你可以在泛型聲明中繼承它:

function printName<T extends NameArgs>(arg: T) {
  console.log(arg.length);
  return arg;
}

這告訴TypeScript,可使用任何具有length屬性的類型。 定義它之後,函數調用語句也必須更改,因為它不再適用於所有類型。 所以它應看起來是這樣:  

 

printName({length: 1, value: 3});

這是一個很基礎的例子。但理解了它,你就能看到在使用泛型時,設置泛型約束是多麼有用。

為什麼是泛型

一個活躍於Stack Overflow社區的成員,Behrooz,在後續內容中很好的回答了這個問題。在TypeScript中使用泛型的主要原因是使類型,類或介面充當參數。 它幫助我們為不同類型的輸入重用相同的代碼,因為類型本身可用作參數。

泛型的一些好處有:

  • 定義輸入和輸出參數類型之間的關係。比如

 

function test<T>(input: T[]): T {
  //… 
}

 

允許你確保輸入和輸出使用相同的類型,儘管輸入是用的數組。

  • 可使用編譯時更強大的類型檢查。在上訴示例中,編譯器讓你知道數組方法可用於輸入,任何其他方法則不行。
  • 你可以去掉不需要的強制類型轉換。比如,如果你有一個常量列表:
Array<Item> a = [];

變數數組時,你可以由智能感知訪問到Item類型的所有成員。

其他資源

結論

你已經看完了泛型概念的概述,並看到了各種示例來幫助揭示它背後的思想。 起初,泛型的概念可能令人困惑,我建議,把本文再讀一遍,並查閱本文所提供的額外資源,幫助自己更好地理解。泛型是一個很棒的概念,可以幫助我們在JavaScript中,更好地控制輸入和輸出。請快樂地編碼吧!


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 永不放棄,永不放棄又有兩個原則,第一個原則是永不放棄,第二個原則就是:當你想放棄時回頭看第一個原則。 概覽 做過web性能優化的同學,對性能優化大殺器gzip應該不陌生。瀏覽器向伺服器發起資源請求,比如下載一個js文件,伺服器先對資源進行壓縮,再返回給瀏覽器,以此節省流量,加快訪問速度。 瀏覽器通過 ...
  • 編譯和解釋 var a = 0; console.log(a); var b = "abc"; 編譯: 一次性把代碼轉換成 CPU 可以看懂的語言,一行一行執行; 解釋:一行一行解析,解析一行執行一行; C、 C++、 C 、 Java 屬於編譯型語言。 在速度方面編譯型語言更快,所以 JavaSc ...
  • 1、 什麼是路由? 註意:作為vue的插件,需要單獨引入js文件,且必須在vue.js之後引入。 <router-link to=“跳轉路徑”></router-link>:該標簽會預設被解析成<a>標簽 <router-view></router-view>:該標簽用於展示組件中的內容,是路由的出 ...
  • 一半出現於view嵌套view的情況,當父子控制項的點擊都設置為 bindtap的時候,會出現點擊觸發了父view的點擊監聽。 要想父子view各監聽到自己的實踐,需要將子view的點擊改為catchtap ,並添加:hover-stop-propagation='true' , 父容器繼續用bind ...
  • 序號 類目 關鍵詞 操作 {% f... ...
  • 今天遇到了這個方法,便去度娘瞭解了下 函數功能:該函數在屬於當前線程的指定視窗里設置滑鼠捕獲。一旦視窗捕獲了滑鼠,所有滑鼠輸入都針對該視窗,無論游標是否在視窗的邊界內。同一時刻只能有一個視窗捕獲滑鼠。如果滑鼠游標在另一個線程創建的視窗上,只有當滑鼠鍵按下時系統才將滑鼠輸入指向指定的視窗。 setCa ...
  • 需求 在開發vue的項目中有遇到了這樣一個需求:一個視頻列表頁面,展示視頻名稱和是否收藏,點擊進去某一項觀看,可以收藏或者取消收藏,返回的時候需要記住列表頁面的頁碼等狀態,同時這條視頻的收藏狀態也需要更新, 但是從其他頁面進來視頻列表頁面的時候不緩存這個頁面,也就是進入的時候是視頻列表頁面的第一頁 ...
  • 一、什麼是組件? 組件 (Component) 是 Vue.js 最強大的功能之一。組件可以擴展 HTML 元素,封裝可重用的代碼。在較高層面上,組件是自定義元素,Vue.js 的編譯器為它添加特殊功能。在有些情況下,組件也可以表現為用 is 特性進行了擴展的原生 HTML 元素。 <body> < ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...