TypeScript: Generic Types and Type Alias

此篇要來談談 TypeScript 通用型別 (Generic Types) 的用途,以及 interface 和型別別名 (Type Alias) 的使用差異。

通用型別 (Generic Types)

有時我們想在具有多種型別可能的情況下重用 (reuse) 程式碼,例如以下這個例子:

1function identity(arg: number): number {
2  return arg;
3}

identify 函式收到參數 arg,並回傳該參數。如果想更有彈性地使用函式,比方說不限定參數型別,則我們可以使用 any

1function identity(arg: any): any {
2  return arg;
3}

但如此一來我們便無法限制回傳值的型別要等同輸入值的型別,這時正是使用通用型別的時機。

我們使用 type variable T 來代表型別,限制了函式輸入和輸出值的型別必須相同:

1function identify<T>(arg: T): T {
2  return arg;
3}

使用 type variable 時,因為無從事先得知變數的型別,所以無法操作屬性或方法,例如以下範例將會收到錯誤:

1function identify<T>(arg: T): T {
2  console.log(arg.length); // error: Property 'length' does not exist on type 
3  return arg;
4}

不過如果是:

1function loggingIdentity<T>(args: T[]): T[] {
2  console.log(args.length);
3  return args;
4}

將不會有問題,因為 arrays of type T 確定有 length 屬性。

通用型別當然也可以跟 interface, class, tuple 等搭配運用,詳細使用方式請見官方文件

介面 (Interface) 與型別別名 (Type Alias) 的異同

型別別名在使用上和 interface 非常相似。

相似之處

typeinterface 一樣可以用來定義物件格式:

1type Animal = {
2  name: string
3}

type 可使用 intersections 延伸屬性(interface 則通常會使用 extends)

 1type Animal = {
 2  name: string
 3}
 4
 5type Bear = Animal & { 
 6  honey: boolean 
 7}
 8
 9const bear = getBear();
10bear.name;
11bear.honey;

不同之處

Aliasing doesn’t actually create a new type - it creates a new name to refer to that type. Aliasing a primitive is not terribly useful, though it can be used as a form of documentation.

根據官方文件type 看起來更推薦用在單純要表示偏靜態的資料格式之時:

 1// declaration
 2type GreetingLike = string | (() => string) | MyGreeter;
 3
 4declare function greet(g: GreetingLike): void;
 5
 6// usage
 7function getGreeting() {
 8  return "howdy";
 9}
10
11class MyGreeter extends Greeter {
12  ...
13}
14
15greet("hello");
16greet(getGreeting);
17greet(new MyGreeter());

References