天天看點

【TS】536- TypeScript 分身之術

閱讀須知:本文示例的運作環境是 TypeScript 官網的 Playground,對應的編譯器版本是 v3.8.3。

一、可愛又可恨的聯合類型

由于 JavaScript 是一個動态語言,我們通常會使用不同類型的參數來調用同一個函數,該函數會根據不同的參數而傳回不同的類型的調用結果:

function add(a, b) {
return a + b;
}

add(1, 2); // 3
add("1", "2"); //"12"      

由于 TypeScript 是 JavaScript 的超集,是以以上的代碼可以直接在 TypeScript 中使用,但當 TypeScript 編譯器開啟 ​

​noImplicitAny​

​ 的配置項時,以上代碼會提示以下錯誤資訊:

Parameter 'x' implicitly has an 'any' type.
Parameter 'y' implicitly has an 'any' type.      

該資訊告訴我們參數 x 和參數 y 隐式具有 ​

​any​

​​ 類型。為了解決這個問題,我們可以為參數設定一個類型。因為我們希望 ​

​add​

​​ 函數同時支援 string 和 number 類型,是以我們可以定義一個 ​

​string | number​

​ 聯合類型,同時我們為該聯合類型取個别名:

type Combinable = string | number;      

在定義完 Combinable 聯合類型後,我們來更新一下 ​

​add​

​ 函數:

function add(a: Combinable, b: Combinable) {
if (typeof a === 'string' || typeof b === 'string') {
return a.toString() + b.toString();
  }
return a + b;
}      

為 ​

​add​

​​ 函數的參數顯式設定類型之後,之前錯誤的提示消息就消失了。那麼此時的 ​

​add​

​ 函數就完美了麼,我們來實際測試一下:

const result = add('Semlinker', ' Kakuqo');
result.split(' ');      

在上面代碼中,我們分别使用 ​

​'Semlinker'​

​​ 和 ​

​' Kakuqo'​

​​ 這兩個字元串作為參數調用 add 函數,并把調用結果儲存到一個名為 ​

​result​

​​ 的變量上,這時候我們想當然的認為此時 result 的變量的類型為 string,是以我們就可以正常調用字元串對象上的 ​

​split​

​ 方法。但這時 TypeScript 編譯器又出現以下錯誤資訊了:

Property 'split' does not exist on type 'Combinable'.
Property 'split' does not exist on type 'number'.      

很明顯 ​

​Combinable​

​​ 和 ​

​number​

​​ 類型的對象上并不存在 ​

​split​

​ 屬性。問題又來了,那如何解決呢?這時我們就可以利用 TypeScript 提供的函數重載特性。

二、函數重載

函數重載或方法重載是使用相同名稱和不同參數數量或類型建立多個方法的一種能力。要解決前面遇到的問題,方法就是為同一個函數提供多個函數類型定義來進行函數重載,編譯器會根據這個清單去處理函數的調用。

function add(a: number, b: number): number;
function add(a: string, b: string): string;
function add(a: string, b: number): string;
function add(a: number, b: string): string;
function add(a: Combinable, b: Combinable) {
if (typeof a === 'string' || typeof b === 'string') {
return a.toString() + b.toString();
  }
return a + b;
}      

在以上代碼中,我們為 add 函數提供了多個函數類型定義,進而實作函數的重載。之後,可惡的錯誤消息又消失了,因為這時 result 變量的類型是 ​

​string​

​ 類型。在 TypeScript 中除了可以重載普通函數之外,我們還可以重載類中的成員方法。

方法重載是指在同一個類中方法同名,參數不同(參數類型不同、參數個數不同或參數個數相同時參數的先後順序不同),調用時根據實參的形式,選擇與它比對的方法執行操作的一種技術。是以類中成員方法滿足重載的條件是:在同一個類中,方法名相同且參數清單不同。下面我們來舉一個成員方法重載的例子:

class Calculator {
  add(a: number, b: number): number;
  add(a: string, b: string): string;
  add(a: string, b: number): string;
  add(a: number, b: string): string;
  add(a: Combinable, b: Combinable) {
if (typeof a === 'string' || typeof b === 'string') {
return a.toString() + b.toString();
  }
return a + b;
  }
}

const calculator = new Calculator();
const result = calculator.add('Semlinker', ' Kakuqo');      

這裡需要注意的是,當 TypeScript 編譯器處理函數重載時,它會查找重載清單,嘗試使用第一個重載定義。如果比對的話就使用這個。是以,在定義重載的時候,一定要把最精确的定義放在最前面。另外在 Calculator 類中,​

​add(a: Combinable, b: Combinable){ }​

​ 并不是重載清單的一部分,是以對于 add 成員方法來說,我們隻定義了四個重載方法。

三、構造函數重載

在 TypeScript 類中構造函數是一種特殊的函數,用于構造指定類的對象。對于構造函數來說,它也是支援重載的,下面我們直接看個示例:

interface Shape {
  x: number;
  y: number;
  height: number;
  width: number;
}

class Square {
public x: number;
public y: number;
public height: number;
public width: number;

constructor();
constructor(obj: Shape);
constructor(obj?: any) {
    this.x = obj?.x ?? 0;
    this.y = obj?.y ?? 0;
    this.height = obj?.height ?? 0;
    this.width = obj?.width ?? 0;
  }
}      

在以上代碼中,我們重載了 Square 類的構造函數,以支援不同的構造方式。接下來我們來驗證一下不同構造方式:

1. 無參構造方式

let square1: Square;
square1 = new Square();
square1.x = 10;
square1.y = 50;
square1.height = 100;
square1.width = 100;      

2. 使用 Shape 類型的對象進行構造

let squareConfig: Shape;
squareConfig = { x: 10, y: 50, height: 100, width: 100 };
let square2: Square;
square2 = new Square(squareConfig);

let square3: Square;
square3 = new Square({ x: 10, y: 50, height: 100, width: 100 });      

四、特定重載簽名

我們可以使用一個特定的簽名來建立具有同樣名稱、參數數量但是有不同的傳回類型的多個函數。為了建立一個特定簽名,必須将函數的參數類型指定為一個字元串。這個字元串用于定義哪個函數重載被調用:

// typescript/lib/lib.dom.d.ts
createEvent(eventInterface: "KeyboardEvent"): KeyboardEvent; // 特定重載簽名
createEvent(eventInterface: "MouseEvent"): MouseEvent; // 特定重載簽名
createEvent(eventInterface: "TouchEvent"): TouchEvent; // 特定重載簽名
createEvent(eventInterface: string): Event; // 非特定重載簽名      

在這個例子中,我們為函數 createEvent 聲明了三個特定重載簽名和一個非特定重載簽名。當在一個對象中聲明特定簽名時,這個對象中必須被賦予至少一個非特定重載簽名。且在編寫重載簽名時,必須在最後列出非重載簽名。

五、參考資源

  • tslang.cn - functions
  • typescript-function-overloads
  • TypeScript 中的方法重載

繼續閱讀