本篇將來介紹「Type Alias 型別別名」,並會拿 interface 來做對照,讓大家更理解兩者的差異、使用時機
這裡順便附上 interface 的文章,想了解的可以點進去瞧瞧
🔗 Day20 - interface 介面
我們在前面的篇章,介紹了許多 TypeScript 的型別,例如 Tuple、Union Type、interface 等
那麼我們可以為這些型別,自定義型別的名稱嗎?
答案是可以的,此時型別別名(Type Aliases)就派上用場啦~
Type Aliases
是 TypeScript 獨有的型別,使用 type
關鍵字就可以來自定義一個型別名稱
它不只可以為任何型別定義一個新名稱,還可以建立更複雜的型別結構,尤其是複雜的物件或聯合型別
等等下面的範例會一一來說明
使用關鍵字 type
來宣告一個型別別名,名稱要「大寫」開頭,例如:Type Person ...
屬性間用「分號」、「逗號」甚至「省略不寫」都是可以的,看團隊撰寫的風格而定。分號或省略不寫在 TypeScript 中更為常見(interface 也是)
Point
是一個「物件型別別名」,用來描述一個具有 x
和 y
屬性的物件type Point = {
x: number;
y: number;
};
const center: Point = { x: 0, y: 0 }; // ✅ Pass
筆者一開始學的時候常會忘記要在宣告後加上 =
,因為 interface 是不需要 =
的,還不熟悉時經常搞混XD
ID
是一個型別別名,代表 number | string
的聯合型別type ID = number | string;
為什麼會這樣說呢?
我們先來看看這個擷取自官方文件的範例
完整的 code 如下
declare function getInput(): string;
declare function sanitize(str: string): string;
type UserInputSanitizedString = string;
function sanitizeInput(str: string): UserInputSanitizedString {
return sanitize(str);
}
// Create a sanitized input
let userInput = sanitizeInput(getInput());
// Can still be re-assigned with a string though
userInput = "new input";
把上面這段拆解
UserInputSanitizedString
被定義為 string
的一個別名type UserInputSanitizedString = string;
UserInputSanitizedString
實際上和 string
是一模一樣的。當使用這個別名來定義函式回傳或變數時,TypeScript 只會讀到背後的原始型別,也就是 stringfunction sanitizeInput(str: string): UserInputSanitizedString {
return sanitize(str); // sanitize() 接收一個字串型別的參數
}
userInput
的型別為 UserInputSanitizedString
,它仍然可以被賦值為「普通的字串」,對 TypeScript 而言,UserInputSanitizedString 和 string 都是同一個型別,只是名稱上的差異而已簡單來說,就是換湯不換藥啦!
let userInput = sanitizeInput(getInput()); // sanitizeInput() 會回傳型別別名 UserInputSanitizedString
userInput = "new input";
「型別別名」為定義複雜的型別提供了一種更彈性的方式,同時也讓語義變得更明確,增加可讀性
然而,它並不是在創建一個「新」的型別,這點跟 interface 不一樣,interface 是可以創建具有特定屬性和方法的「新型別」的
假設你正在開發一個電商服務,需要處理多種產品的資訊,而每個產品其實都存著在一些共同的屬性,例如:id、商品名稱、價格、庫存量
在不同的功能中,可能會需要重複使用這些產品的屬性結構,這時就可以把這些共同屬性抽出來定義成一個型別別名,像是底下的 Product
type Product = {
id: number;
name: string;
price: number;
stock: number;
};
// 把型別別名 Product 套用在變數上
let products: Product[] = [];
// 定義一個函式來新增產品到陣列中,傳入的產品參數要符合 Product type 的結構
function addProduct(product: Product) {
products.push(product);
}
// ❌ Error
addProduct({ id: 1, name: '北拋拋幼咪咪面膜', price: 499 });
// ✅ Pass
addProduct({ id: 2, name: '保濕精華乳', price: 999, stock: 100 });
可以看到,Product
型別別名被用於多個函式中來代表產品屬性
在需要修改產品屬性結構時,只需要修改 Product 型別別名就好,不用一一修改使用到這些屬性的地方
假設你正在開發一個有多種狀態的 UI 元件,例如按鈕有各種不同的狀態,例如「成功」、「失敗」、「載入中」。我們可以使用型別別名來定義這些狀態
這裡的 ButtonState
型別別名用來表示按鈕的不同狀態
type ButtonState = 'successful' | 'fail' | 'loading';
接著,我們可以使用這個型別別名來管理 UI 元件的狀態:
function renderButton(state: ButtonState) { // ButtonState 型別別名
switch (state) {
case 'successful':
console.log('成功狀態的按鈕');
break;
case 'fail':
console.log('失敗狀態的按鈕');
break;
case 'loading':
console.log('載入狀態的按鈕');
}
}
renderButton('successful'); // ✅ 成功狀態的按鈕
renderButton('fail'); // ✅ 失敗狀態的按鈕
renderButton('loading'); // ✅ 載入狀態的按鈕
renderButton('test'); // ❌ Argument of type '"test"' is not assignable to parameter of type 'ButtonState'.
renderButton
函式根據傳入的 ButtonState
狀態來決定如何渲染按鈕
如果需要增加新的按鈕狀態,只需在 ButtonState
型別別名中增加新的狀態,並在 renderButton 函式中加入對應的處理邏輯就好囉~
若不小心將錯誤的狀態傳遞給按鈕,會在編譯時就補捉到錯誤
intersection types (&)
擴展其他屬性、方法, interface 則是透過 extends
去擴展type Container<T> = T extends string ? StringContainer : NumberContainer;
特性 | Type Alias | Interface |
---|---|---|
定義 | 可以為任何型別賦予別名 | 用來定義物件的形狀(包含物件屬性、方法) & 函式簽名 |
是否可擴展 | 是,透過 intersections 去擴展 | 是,透過 extends 去擴展 |
是否可重複定義 | 否 | 是,同名的介面會自動合併 |
每天的內容有推到 github 上喔