第一週第三天:探索型別推論與聯合型別

第一週第三天:探索型別推論與聯合型別

學習資源

(註:今天在翻閱這次的學習資源時,才發現昨天在讀任意值型別的過程中,似乎已經將型別推論講完了耶,不過沒關係可以當作複習。)


什麼是型別推論(Type Inference)?

在沒有明確指定型別的情況下,TypeScript 會根據程式碼中的上下文和賦值情況來判斷型別。以下舉幾個例子:

這是在寫例子的時冒出一個想法:想像我們戴上 TypeScript 眼鏡,就能在程式碼內看見 TypeScript 透過型別推論判斷出的型別。TypeScript 眼鏡,帶你看見全世界。

變數型別推論

let message = "Hello, World!"; // TypeScript 推斷型別為 string
let age = 30; // TypeScript 推斷型別為 number
let isEnabled = true; // TypeScript 推斷型別為 boolean

戴上 TypeScript 眼鏡後會看到:

let message: string = "Hello, World!";
let age: number = 30;
let isEnabled: boolean = true;

函式參數和返回值型別推論

function sum(a, b) {
  return a + b;
}

戴上 TypeScript 眼鏡後會看到:

function sum(a: number, b: number): number {
  return a + b;
}

未宣告型別的變數型別推論

let someVariable;

戴上 TypeScript 眼鏡後會看到:

let someVariable: any;

什麼是聯合型別(Union Types)?

聯合型別是 TypeScript 中的一種進階型別,用來表示允許將多個不同的型別組合成一個型別。聯合型別用 | 符號來表示多個型別的組合。

(註:可以想像戴 TypeScript 眼鏡來看 JavaScript 的 OR 運算符號 || 時會少一個 | 來記)

function displayValue(value: string | number) {
  console.log(value);
}

displayValue("Hello"); // 🟢 正確:value 可以是 string
displayValue(42); // 🟢 正確:value 也可以是 number
displayValue(true); // 🔴 錯誤:value 不能是 boolean,因為它不是 string 或 number 的一部分

為什麼需要聯合型別?

(註:這是 ChatGPT 建議補充說明的部份,它用實際開發可能會遇到的需求當做範例,超有代入感,這個方式很讚學起來)

聯合型別讓 TypeScript 可以更靈活地處理變數具有多種不同型別的情況,以下是一個實際開發可能會遇到的應用場景:

假設我們正在開發一個線上商店的應用程式,其中一個功能是計算折扣後的價格。折扣可以是固定金額(例如,10元)或百分比(例如,10%)。為了處理這個情況,我們可以使用聯合型別來表示折扣:

function calculateDiscountedPrice(
  price: number,
  discount: number | string
): number {
  if (typeof discount === "string") {
    // 如果折扣是字串(例如 "10%"),則將其轉換為小數並應用
    const percentage = parseFloat(discount) / 100;
    return price * (1 - percentage);
  } else {
    // 如果折扣是數字(例如 10),則直接從價格中減去折扣
    return price - discount;
  }
}

console.log(calculateDiscountedPrice(100, "10%")); // 90
console.log(calculateDiscountedPrice(100, 10)); // 90

在這個例子中,我們使用聯合型別 Discount 來表示折扣,它可以是數字(固定金額)或字串(百分比)。calculateDiscountedPrice 函數根據折扣的型別計算折扣後的價格,從而滿足實際開發中的需求。

存取聯合型別共有的屬性和方法注意事項

在處理聯合型別的變數時,TypeScript 只允許存取所有型別共有的屬性和方法。

直接用一個會報錯的例子來說明:

function getLength(input: string | number): number {
  return input.length; // 🔴 錯誤:因為 length 不是 string 和 number 的共有屬性
}

要解決這個錯誤,有兩個方式:

  1. 在函式內部對輸入進行型別檢查,以確保只在 input 為字串時存取 length 屬性

     function getLength(input: string | number): number {
       if (typeof input === "string") {
         return input.length; // 正確:input 為 string,可以訪問 length 屬性
       } else {
         return 0; // 如果 input 是數字,我們返回 0 或其他適當的值
       }
     }
    
  2. 或是改用 stringnumber 兩種型別都有的方法 toString()

     function getLength(input: string | number): number {
       return input.toString().length;
     }
    

聯合型別變數賦值時與型別推論之間擦出的火花

一樣用例子來說明,我們先定義一個聯合型別的變數:

let mixedValue: number | string;

這個聯合型別變數 mixedValue 表示它可以是 numberstring 型別。接著,我們為這個變數賦值:

mixedValue = "hello"; // TypeScript 推論 mixedValue 為 string 型別

當我們把字串 "hello" 賦值給 mixedValue 變數時,TypeScript 會根據賦值來推論 mixedValue 的型別。因為 "hello" 是一個字串,所以 TypeScript 推論 mixedValuestring 型別。

如果我們再次為 mixedValue 賦值:

mixedValue = 42; // TypeScript 推論 mixedValue 為 number 型別

這次我們把數字 42 賦值給 mixedValue 變數。因為 42 是一個數字,所以 TypeScript 會推論 mixedValuenumber 型別。

小心擦出危險的火花?

其實就是在上一段提到的注意事項:TypeScript 只允許存取所有型別共有的屬性和方法。

let mixedValue: number | string;

mixedValue = "hello";
console.log(mixedValue.length); // 🟢 不會報錯,因為 mixedValue 被推斷為 string 型別

mixedValue = 42;
console.log(mixedValue.length); // 🔴 會報錯,因為 length 屬性不存在於 number 型別

後記:為了確保自己能以初次翻閱這本書的狀態來紀錄筆記,我在計畫開始時並沒有事先瀏覽所有內容,只有掃過一遍目錄而已,所以每次在準備學習和寫筆記時,我才會知道內容的多寡和複雜程度。這過程有點像是開盲盒,不確定究竟打開會是驚喜(簡單)還是驚嚇(複雜)。

儘管內心難免會有些負擔,但我想這樣做才能捕捉並保留在第一次閱讀時所出現的疑問、靈感和想法,就算之後全部忘光再回來複習,我希望能透過筆記再次理解這些知識。