TypeScript入門 - 7

by vivid 20. 三月 2019 11:43

.NET Magazine國際中文電子雜誌
作 者:許薰尹
審 稿:張智凱
文章編號:
N190320502
出刊日期: 2019/3/20

在物件導向程式語言,如C#、Java之中,為了達到程式碼的重複使用性,支援一個特殊的型別--「泛型(Generic)」,以讓程式設計師,在設計元件時,可以指定想要使用的型別。這一篇文章中將介紹TypeScript中的泛型以及型別推論(Type Inference)行為。

我們先來看一個非泛型的範例,參考以下範例程式碼,「showData」函式有一個字串型別的引數,「showData」函式將會回傳字串型別的資料:

function showData( a: string ): string {
  return a;
}

let s: string = "10";
let r: string = showData( s );

console.log(r) // 10


 

若叫用「showData」函式時,傳入的不是字串型別的參數,便會產生語法的錯誤,請參考下圖所示:

clip_image002

圖 1:參數型別不相符的錯誤。

若想在爾後叫用「showData」函式時,傳遞數值當參數,且回傳數值型別的資料,可能需要將程式改寫如下:

function showData( a: number ): number {
  return a;
}

let s: number = 10;
let r: number = showData( s );

console.log( r ) // 10


 

那麼當參數型別可能是數值,又可能是字串時,該如何呢? 這樣的做法可會造成你可能需要為不同型別的參數與回傳值來建立不同名稱的方法以解決問題,此種解決方案會讓「showData」函式較不易重複使用。當然在TypeScript中包含一個特殊的「any」型別,我們可以將「showData」函式的傳入參數與回傳值改用「any」型別,參考以下範例程式碼:

function showData( a: any ): any {
  return a;
}

let s1: number = 10;
let r1: number = showData( s1 );
console.log( r1 ); // 10

let s2: string = "10";
let r2: string = showData( s2 );
console.log( r2 ); // 10


 

「any」型別感覺上和泛型型別的概念很相像,如此「showData」函式可以接收任意型別的參數,也可以回傳任意型別的值。不過,認真的思考一下,當你傳入數值參數到「showData」函式時,你將無法檢查回傳值是何種型別,因為「any」意味著任意型別,可能回傳的型別是字串型別;同樣的,若傳入字串參數到「showData」函式時,你將無法檢查回傳值是否為字串型別。

較好的做法是利用泛型(Generic),讓我們將上述的「showData」函式程式碼,改寫成泛型函式(Generic function)。參考以下範例程式碼,「showData」泛型函式有一個型別參數(type parameter )名為「T」,有一個引數(argument)名為「a」,其型別為「T」,「showData」泛型函式回傳值的型別為「T」:

function showData<T>( a: T ): T {
  return a;
}

let s1: number = 10;
let r1: number = showData<number>( s1 );
console.log( r1 ); // 10

let s2: string = "10";
let r2: string = showData<string>( s2 );
console.log( r2 ); // 10


範例程式中叫用「showData」泛型函式時,則分別將「T」型別參數(type parameter )繫結到「number」與「string」兩種型別。這樣我們便可以在撰寫程式碼時,讓TypeScript檢查叫用「showData」泛型函式時,傳入的參數型別和回傳值的型別是否相符。若參數的型別是數值,而將「showData」函式回傳值指派給字串型別的變數,就會得到錯誤,請參考下圖所示:

clip_image004

圖 2:型別不相符錯誤。

此外你還可以簡化程式碼,在呼叫「showData」函式時,可以省略掉 < > 符號以及型別參數(type parameter ),上述的程式可以改寫如下,使其更為簡短易讀:

function showData<T>( a: T ): T {
  return a;
}

let s1: number = 10;
let r1: number = showData( s1 );
console.log( r1 ); // 10

let s2: string = "10";
let r2: string = showData( s2 );
console.log( r2 ); // 10

 

 

泛型類別(Generic Class)

在TypeScript之中,和C#、Java程式語言類似,類別也支援泛型,我們稱之為泛型類別(Generic Class)。泛型類別(Generic Class)定義時,一樣使用「class」關鍵字開始,隨後是類別名稱,在類別名稱後置 < > 符號,在 < > 之中可以定義泛型型別參數(generic type parameter)。參考以下範例程式碼,定義一個「GenericClass」類別,類別之中包含一個「data」屬性,以及一個「getData」函式。「data」屬性的型別與「getData」函式的引數、回傳值的型別必需要相符:

class GenericClass<T> {
  data: T;
  getData: ( a: T ) => T;
}

let o1 = new GenericClass<number>();
o1.data = 100;
o1.getData = function ( a ) {
  return o1.data + a;
}
let r1: number = o1.getData( 100 );
console.log( r1 ) // 200

let o2 = new GenericClass<string>();
o2.data = "100";
o2.getData = function ( a ) {
  return o2.data + a;
}
let r2: string = o2.getData( "100" );
console.log( r2 ) //100100


 

「GenericClass」類別中的「data」屬性與「getData」函式的回傳值型別不受限,例如上述範例中所展示的一般,可以是「number」或「string」型別。使用泛型類別(Generic Class)的好處是,你可以限定「data」屬性,以及「getData」函式的參數與回傳值,都必需是相同的型別。只要型別不相符,TypeScript型別檢查器便會回報型別不符的錯誤。

型別參數的條件約束(Constraint)

在上一個範例之中,「GenericClass」類別的型別參數可以是任意型別,所以我們可以將型別參數設定為「boolean」型別,例如下列程式碼:

class GenericClass<T> {
  data: T;
  getData: (a: T) => T;
}

let o1 = new GenericClass<boolean>();
o1.data = true;
o1.getData = function ( a ) {
  return o1.data + a;
}
let r1: boolean = o1.getData( false );
console.log( r1 )

 

不過這樣會造成「getData」函式的程式碼出現問題,因為「+」運算子並不支援「boolean」型別,TypeScript型別檢查器將顯示語法錯誤,請參考下圖所示:

clip_image006

圖 3:「+」運算子並不支援「boolean」型別。

若想要限定「GenericClass」類別中的型別參數(Type Parameter)只能夠是「number」與「string」型別,不可以是其它型別,那麼你可以使用「extends」關鍵字來指明條件約束(Constraint),參考以下範例程式碼:

class GenericClass<T extends string | number> {
  data: T;
  getData: ( a: T ) => T;
}

let o1 = new GenericClass<number>();
o1.data = 100;
o1.getData = function ( a ) {
  return o1.data + a;
}
let r1: number = o1.getData( 100 );
console.log( r1 ) // 200

let o2 = new GenericClass<string>();
o2.data = "100";
o2.getData = function ( a ) {
  return o2.data + a;
}
let r2: string = o2.getData("100");
console.log( r2 ) // 100100


現在建立「GenericClass」物件時,如果型別參數不是「string」或「number」,而是「boolean」型別,型別檢查的結果將會顯示錯誤訊息,請參考下圖所示:

clip_image008

圖 4:違反型別參數的條件約束(Constraint)錯誤。

型別推論(Type Inference)

最後我們來談談一下TypeScript提供的型別推論(Type Inference)功能,以助於了解TypeScript如何解讀變數的型別。假設有一行程式碼如下,宣告一個「x」變數,指定其值為數值「100」:

let x = 100;

使用Visual Studio Code開發TypeScript程式時,只要將滑鼠停留在變數上方,就會顯示型別資訊,請參考下圖所示,「x」變數的型別會被視為「number」型別:

clip_image010

圖 5:自動推論變數最佳型別。

若程式碼如下,宣告一個「x」變數,指定其值為字串「"100"」:

let x = "100";

則「x」變數的型別會被視為字串,請參考下圖所示:

clip_image012

圖 6:自動推論變數最佳型別。

同理,則以下這行程式碼,其中的「x」變數的型別會被視為「boolean」型別:

let x = true;

推論的結果,請參考下圖所示:

clip_image014

圖 7:自動推論變數最佳型別。

以下這行程式碼,其中的「x」變數的型別會被視為「string [ ]」型別:

let x = ["Mary", "Candy", "Lili"];

推論的結果,請參考下圖所示:

clip_image016

圖 8:自動推論變數最佳型別。

但有一個特殊的情況,如果陣列中包含不同型別的項目時,推論的結果可能會有所不同,TypeScript會根據陣列中的項目來找出最佳的型別,例如以下範例程式碼,「x」與「y」會被視為「string []」型別,陣列中包含多個字串型別的項目:

let x = ["Mary", "Candy", "Lili", null];

let y = ["Mary", "Candy", "Lili", undefined];

但若程式碼修改如下,陣列中包含字串與布林型別項目:

let x = ["Mary", "Candy", "Lili", true, false];

則「x」會被視為聯合陣列型別(Union Array Type)推論的結果,請參考下圖所示:

clip_image018

圖 9:自動推論變數最佳型別。

不過我們再看一個範例,若有「Sales」、「Accountant」類別皆是繼承自「Employee」類別,而「x」變數則是存放「Sales」、「Accountant」實體:

class Employee {

}

class Sales extends Employee {

}

class Accountant extends Employee {

}

let e1 = new Sales();

let e2 = new Accountant();

let x = [e1, e2];

此時「x」變數會被視為聯合陣列型別(Union Array Type),推論的結果,請參考下圖所示:

clip_image020

圖 10:自動推論變數最佳型別。

比較好的做法是將「x」視為「Employee [ ]」型別,但因為陣列中沒有「Employee」型別,TypeScript無法推論出適當的型別,就將「x」視為聯合陣列型別(Union Array Type)。我們可以明確在宣告「x」陣列時,明確指定型別來解決這個問題,可修改程式碼如下:

class Employee {

}

class Sales extends Employee {

}

class Accountant extends Employee {

}

let e1 = new Sales();

let e2 = new Accountant();

let x: Employee[] = [e1, e2];

若「x」陣列中有包含「Employee」物件,參考以下範例程式碼:

class Employee {

}

class Sales extends Employee {

}

class Accountant extends Employee {

}

let e1 = new Sales();

let e2 = new Accountant();

let e3 = new Employee();

let x = [e1, e2 , e3];

TypeScript就可以推論出「x」最佳型別是「Employee []」型別,推論的結果,請參考下圖所示:

clip_image022

圖 11:自動推論變數最佳型別。

Tags:

.NET Magazine國際中文電子雜誌 | TypeScript | 許薰尹Vivid Hsu

新增評論




  Country flag
biuquote
  • 評論
  • 線上預覽
Loading






NET Magazine國際中文電子雜誌

NET Magazine國際中文電子版雜誌,由恆逸資訊創立於2000,自發刊日起迄今已發行超過500篇.NET相關技術文章,擁有超過40000名註冊讀者群。NET Magazine國際中文電子版雜誌希望藉於電子雜誌與NET Developer達到共同學習與技術新知分享,歡迎每一位對.NET 技術有興趣的朋友們多多支持本雜誌,讓作者群們可以有持續性的動力繼續爬文。<請加入免費訂閱>

月分類Month List