本篇要來講解「聯合型別」和「交集型別」這兩親戚
讓大家了解兩者的差別、使用時機,以及使用上該注意的地方
請原諒我的圖真的畫得很醜XD
在前面文章的範例中,聯合型別已經不時的出場很多次,那今天就來做更多的介紹
使用時機:當你想定義兩個以上(含)的型別時,會用
|
來隔開各個型別
let id: string | number; // 聯合型別
id = "12345";
id = 333;
id = true; // ❌ Type 'boolean' is not assignable to type 'string | number'.
function displayPrice(price: number | [number, number]) { // 聯合型別
if (typeof price === "number") {
console.log(`價格:${price}`);
} else {
console.log(`價格範圍:${price[0]}~${price[1]}`);
}
};
displayPrice(50);
displayPrice([33, 55]);
id
的 type 只能是 string
或 number
給予這兩個型別外的值就會報錯
而函式的參數也可以是聯合型別
function printId(id: number | string) {
console.log(id.toUpperCase());
};
這個範例會得到一個錯誤結果,因為 id
若今天傳入的是 number
就無法使用字串的方法 toUpperCase()
解法是可以透過 narrow
的概念來「限縮程式碼的聯合範圍」
那要如何限縮範圍呢?可以透過 typeof
, Array.isArray()
, instanceof
等任何方式來判斷具體型別,並經常搭配 if...else...
的使用,像是下方的例子
function printId(id: number | string) {
if (typeof id === "string") {
console.log(id.toUpperCase());
} else {
console.log(id);
}
};
想看更多很推薦這篇鐵人文章,寫得很清楚 👍🏻
不只是透過明確的使用 |
定義聯合型別
在這篇文章的 enums at compile time
中有提到在「編譯時」可以透過 keyof typeof
去取得 enum 中的所有 key,並轉換成 string Union Type (字串聯合型別),
enum LogLevel {
ERROR,
WARN,
INFO,
DEBUG,
}
type levelKey = keyof typeof LogLevel; // type levelKey = 'ERROR' | 'WARN' | 'INFO' | 'DEBUG';
使用時機:當一個值要同時符合所有給定的型別時,可以使用交集型別,用
&
來組合現有的物件型別(Object Types)
原始型別
id
不可能同時是 string
和 number
,會被推斷為 never
,表示該屬性沒有可接受的值function printId(id: string & number) {
console.log(id);
};
printId('123'); // ❌ Argument of type 'string' is not assignable to parameter of type 'never'.
printId(123); // ❌ 訊息同上
EmployeePerson
是 Person
和 Employee
的交集,也就是說 EmployeePerson
要同時具有 Person
和 Employee
的屬性type Person = {
name: string;
age: number;
};
type Employee = {
employeeId: number;
department: string;
};
type EmployeePerson = Person & Employee; // 交集型別
let employee: EmployeePerson = { // ✅ Pass
name: "John",
age: 30,
employeeId: 12345,
department: "Engineering"
};
department
屬性故意少打最後一個「t」// ... 前面省略
let employee: EmployeePerson = {
name: "John",
age: 30,
employeeId: 12345,
departmen: "Engineering" // department 故意少打最後一個「t」
};
TypeScript 會很貼心地提醒你是不是打錯字🥳
value
在 A
中是 number,在 B
中是 string,無法同時是 number 又是 string,跟「範例 1」是一樣的,會被推斷為 never
TypeScript 會嘗試將「交集型別」的屬性合併。如果屬性兼容,則會成功合併;如果不兼容( = 同一個屬性名稱有不同的型別),則 TypeScript 會報錯!
type A = {
value: number; // 屬性相同
};
type B = {
value: string; // 屬性相同
};
type C = A & B;
let test: C = { value: 123 }; // ❌ Type 'number' is not assignable to type 'never'.
❌ 不要在 overloads(超載)上,分別對「同一個參數」定義多個型別
interface Moment {
utcOffset(): number;
utcOffset(b: number): Moment;
utcOffset(b: string): Moment;
}
✅ 可以使用「聯合型別」
interface Moment {
utcOffset(): number;
utcOffset(b: number | string): Moment;
}
每天講的內容有推到 github 上喔