iT邦幫忙

2022 iThome 鐵人賽

DAY 22
0
Modern Web

Hello TypeScript 菜鳥系列 第 22

Day 21. TypeScript Type What?:Type Alias

  • 分享至 

  • xImage
  •  

2022.10.08 更新
這天誤用type casting一詞,今天緊急刪掉這個主題,因為嚴格來說TypeScript沒有type casting,詳情可見這部影片這篇stackoverflow的內容。


TypeScript Turtorial看到目前為止差不多要結束了,快沒題材可以繼續發文XD

不過在彙整TypeScript關鍵字(keyword)和運算子(operator)的時候,發現有幾個可以一起討論的有趣主題(而且應該能讓我發個四、五天左右的文),這些主題分別是:

  • Type Alias
  • Type Assertion
  • Type Guide
  • Type Predicates

要先知道這幾個主題,才比較好了解TypeScript的關鍵字和運算子在做什麼,那麼今天先從第10天也有提到、比較好了解的Type Alias開始說起。


某些時候TypeScript內建型別不符所需,開發者可能會自行建立一些較複雜的型別。

為了可以重複利用自己建立的複雜型別,TypeScript允許開發者替型別額外取名字,而這些型別額外取的名字就稱為 Type Alias。

替型別取別名的方式類似於替變數命名,以 type 關鍵字開頭,空格後加上型別別名和 =,接著才是自行建立的型別,例如:

type MyType = string | number | boolean;

如此一來 MyType 就可以在檔案中任何地方使用,甚至如果熟悉JavaScript的模組輸入輸出方式,也能將 MyType 輸出,並且讓需要的檔案引入。

例如我在專案中特地建立一個共用型別的檔案 types.ts,裡面放置多個檔案會用到的型別:

// types.ts
export type Code = string | number;
export type ...

然後可以在需要的檔案引入:

// any-file.ts
import { Code } from 'types.ts';

let myCode: Code;

myCode = "1010";	// ok
myCode = 1010;	// ok
myCode = false;	// error

type 關鍵字可以替任何型別命名,所以也可以替一個物件(object)型別取個有意義的名稱,提高程式碼可讀性:

type Position = {
	x: number;
	y: number;
	z: number;
}

function Draw(pos: Position){
	console.log(`draw (${pos.x}, ${pos.y}, ${pos.z}`));
}

Draw({x: 0, y: 100, z: 100});	// draw (0, 100, 100)

看到這裡可能會想說:嘿!這跟第16天用 interface 定義物件的方式有什麼不同?

先回顧一下之前說過的TypeScript型別系統 - Structural Type System,這種系統允許變數型別的 外型(shape) 大致符合,就能說這個變數就是我要的了,譬如我們讓另一個物件輸入Draw函式:

type Position = {
	x: number;
	y: number;
	z: number;
}

function Draw(pos: Position){
	console.log(`draw (${pos.x}, ${pos.y}, ${pos.z}`));
}

Draw({x: 0, y: 100, z: 100, k: -5});	// draw (0, 100, 100), k沒有被使用

如果單純從定義物件 外型(shape) 的角度看待 type 和 interface ,事實上,這兩個關鍵字的作用幾乎是一樣的,甚至可以互相延展(extends)!?譬如 type & interface 或是 interface XXX extends type

若要說 type 和 interface 有什麼不一樣,大致會有以下幾點不同之處:

  1. 兩者都可以相互延展 interface 或 type,只是 type 用 & 運算子,interface 用 extends
  2. 開發時若有型別錯誤的問題,interface 的錯誤訊息會比較明確(例如指出物件少了或多了哪些屬性而型別不符),相較之下 type 的錯誤訊息可能會讓人摸不著頭緒;
  3. 既有的 interface 可以重新宣告,但是 type 一旦命名就不能用同一個名稱重新宣告成另一種型別。

以下舉例第1點和第3點:

// 1.
type Position = {
	x: number;
	y: number;
	z: number;
}

interface Location {
	lat: number;
	lng: number;
}

// type "intersects" interface
type Place = Location & {
	name: string;
	address: string;
}

// interface "extends" type
interace Shape extends Position { 
	width: number;
	length: number;
};


// ============================================= //

// 3.
Position = { a: number; b: number; }	// error

interface Location {	// ok
	x: number;
	y: number;
}

也就是說,兩者若是用在宣告一個物件的樣態時,除了以上幾點,幾乎是一樣的。

不過 type 和 interface 各自還有其他用途,譬如:

  • type 能和其他型別變成一種 union 型別, interface 不行;
  • interface 可以被 class 實作(implements),但 type 不行。

若是從這幾點來看,用 type 和 interface 宣告物件的主要目的仍然是不一樣的,因此開發時還是要考慮一下使用這兩者宣告物件的用途。


參考資料
Type Aliases @TypeScript Handbook
Differences Between Type Aliases and Interfaces @TypeScript Handbook
interface vs type @TS Playground
TypeScript Type Aliases @TypeScript Tutorial
Interfaces vs Types in TypeScript @stackoverflow


上一篇
Day 20. TypeScript Generic 泛型:Generic Constraints
下一篇
Day 22. TypeScript Type What?:Type Assertion
系列文
Hello TypeScript 菜鳥31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言