iT邦幫忙

2024 iThome 鐵人賽

DAY 17
0
JavaScript

我推的TypeScript 操作大全系列 第 17

我推Day17 - 型別安全大升級!用 TypeScript 防禦你的程式世界

  • 分享至 

  • xImage
  •  

https://ithelp.ithome.com.tw/upload/images/20241001/20124462h9nEkbPpQq.jpg


更嚴格的型別限制,為你的程式碼提供最強的防護

今天,我們要來聊聊如何從 TypeScript 的型別安全又更加嚴謹,並確保程式碼乾淨。

你有沒有遇過這種情況:寫程式時,你輸入了錯誤的資料,但編譯器卻沒告訴你,結果這個錯誤悄悄躲過去,讓 bug 跑出來?
🤦‍♀️ 別擔心,今天的技巧可以幫你解決這個問題!

首先,問題來了...

假設你有一個名字清單,程式碼看起來像這樣:

const names = ["王小明", "陳大文", "李小華"];

const findName = (surname: string) => {
  return names.find((name) => name.includes(surname));
};

// 我們可以傳入任何字串,這實在太危險了!

console.log(findName("王")); // 輸出: 王小明
console.log(findName("陳")); // 輸出: 陳大文
console.log(findName("李")); // 輸出: 李小華
console.log(findName("張")); // 輸出: undefined (姓氏不存在)

這段程式碼裡,我們可以隨便傳入任何字串,但如果輸入不存在的姓名,結果就會是 undefined

這種隱晦的錯誤超級討厭,因為它們可能在程式某個角落偷偷潛伏,這種小錯誤雖然不起眼,但長遠來看,可能會導致系統行為異常或者無法追蹤的 bug!

所以,我們應該怎麼解決呢?這時,TypeScript 就閃亮登場了!✨


為什麼這樣做?

除了避免錯誤,這樣的型別限制還能提升程式碼的可讀性和可維護性。

當開發團隊規模增大或功能擴展時,嚴格的型別檢查能確保程式在未來的變動中依然穩定運行。

我們可以用 TypeScript 來確保只允許合法的姓名進入 findName 函式。

這樣編譯器會幫助我們,當我們不小心打錯字或輸入不存在的姓氏時,它會立即報錯,讓你免去未來可能出現的 bug。
這不僅能提升程式碼的安全性,也可以讓開發過程更加流暢。
除了約束型別之外,還會規定哪些「真正的型別」才合法。


直觀的紅綠燈例子

假設我們要確保程式只接受 "red"、"green" 和 "yellow" 這三個字串作為合法輸入,這樣可以避免傳入無效的顏色字串如 "blue" 或 "purple",即使它們是字串型別。

  • 定義紅綠燈狀態型別

我們可以使用字面值型別來定義紅綠燈狀態,像這樣:

type TrafficLight = "red" | "green" | "yellow";

這個型別 TrafficLight 只允許 "red"、"green" 和 "yellow" 這三個值。
接下來,我們來寫一個函式,讓它只接受這些合法的字串作為輸入。

  • 實作函式

現在,我們來寫一個 changeLight 函式,用來改變交通燈的顏色,並確保只接受合法的顏色字串:

function changeLight(color: TrafficLight): string {
  switch (color) {
    case "red":
      return "Stop, the light is red!";
    case "green":
      return "Go, the light is green!";
    case "yellow":
      return "Caution, the light is yellow!";
    default:
      // 這段邏輯永遠不會到達,因為 TypeScript 已經保證只能輸入 "red", "green", "yellow"
      throw new Error("Invalid traffic light color");
  }
}

在這裡,changeLight 函式接收 color 參數,型別是 TrafficLight,這保證只能傳入 "red"、"green" 或 "yellow"。

  • 呼叫函式

接下來,我們來呼叫這個函式並測試:


console.log(changeLight("red")); // 輸出: Stop, the light is red!
console.log(changeLight("green")); // 輸出: Go, the light is green!
console.log(changeLight("yellow")); // 輸出: Caution, the light is yellow!

如果我們試圖傳入無效的顏色,例如 "blue":

console.log(changeLight("blue")); // TypeScript 會報錯,無法編譯!

這樣設計的好處是

  • 型別安全:只允許合法的紅綠燈顏色進入函式,避免因輸入錯誤而引發的潛在錯誤。
  • 可讀性高:當其他開發者看到這段程式碼時,他們能立即知道這個函式只接受三種顏色,減少了理解程式的時間。
  • 早期報錯:TypeScript 會在編譯時期幫助你檢查輸入的正確性,避免程式執行時才發現問題。

就像買東西的時候,如果有人用不存在的會員卡號來購物,系統會馬上告訴你這個卡號是無效的。
這樣的機制放到程式碼裡,就是 TypeScript 幫你檢查資料的安全性啦!

假設系統不做任何檢查,開發者輸入了 'blue' 這種不存在的顏色,這可能會導致應用出現非預期的行為,比如錯誤的燈號顯示甚至崩潰。


解決方案:提取合法的姓氏

回到我們早前的問題裡,我們知道在 findName 裡,我們應該只接受清單中的姓氏,而不是任意的字串。
要做到這一點,我們可以使用 TypeScript 的泛型(Generics)來創建一個自訂型別,叫做 ExtractSurname,用來提取名字清單中的合法姓氏。

先看這段程式碼,它看起來可能有點複雜,但別擔心,我會一步一步幫你解釋清楚:

type ExtractSurname<T extends string> =
  T extends `${infer Firstname} ${infer Surname}` ? Surname : null;

這裡的邏輯可以想像成在一張票券系統裡,我們只會承認真實存在的姓氏,像是票券的「票號」,而不會承認那些不存在的票號。

  1. ExtractSurname 接收一個字串型別 T,這個型別代表任何字串。

  2. 然後我們用 TypeScript 的三元運算符來比較型別。

  3. 簡單來說,我們知道名字的格式是「 姓 名 」(Firstname Surname),因此在這裡,我們使用 infer 關鍵字,來推斷並提取出 FirstnameSurname

    • 如果符合「姓 名」的格式,就返回名字 Surname
    • 否則返回 null

更具體的實用範例

例如,你有一個購物網站,它裡面有很多商品分類。
你可能只想允許系統接受現有的商品分類來進行查詢,而不是允許隨便亂打的分類名稱。

這樣,我們可以確保查詢的效率和安全性。

回到我們的名字範例,現在我們有了一個 ExtractSurname 型別,接下來我們需要創建一個 Surname 型別,代表 names 陣列中的所有合法姓氏:

const names = ["王小明", "陳大文", "李小華"] as const;
type Surname = ExtractSurname<(typeof names)[number]>;
  • 這裡發生了什麼事呢?
  1. 我們使用 as const,讓 names 的型別變成「字面值型別」,這代表每個元素的型別都會是固定的字串,而不再只是一般的 string

  2. 然後 (typeof names)[number] 是一個巧妙的方式,表示我們要取 names 陣列中的每個元素的型別。

  3. 最終,我們得到了這個型別:

type Surname = "王" | "陳" | "李";

升級你的 findName 函式

最後一步就是更新我們的 findName 函式,確保它只接受合法的姓氏。

const findNameUsingSurname = (surname: Surname) => {
  return names.find((name) => name.includes(surname))!;
};

// 現在只有 "王", "陳", "李" 是合法的輸入 
const fullName = findNameUsingSurname("王"); 
console.log(fullName); // 輸出: 王小明

這樣一來,當你不小心輸入錯誤或不存在的值時,TypeScript 編譯器會幫你報錯!
再也不用擔心拼錯字導致奇怪的錯誤啦~


生活化的小例子

想像一下,當你在點餐時,系統只能接受菜單上已有的餐點名稱。
如果你不小心點了個不存在的「麻辣冰淇淋」,系統馬上提醒你這是無效的點單。

同樣地,在程式碼裡,我們也想要確保使用者輸入的資料是正確的,而不是隨便輸入。
無論是會員卡號還是點餐系統,這些實際應用都在提醒我們,資料驗證與型別檢查在程式設計中有多重要!


總結小語

  1. 型別安全:透過 TypeScript 的型別限制,保證只接受合法的輸入,避免不必要的錯誤發生。

  2. 可讀性高:嚴格的型別約束讓程式碼更易於理解,團隊合作時也更容易上手。

  3. 早期錯誤檢測:在編譯時期就能發現輸入錯誤,大幅減少執行時才發現問題的風險。

  4. 提升維護性:即使在未來擴展或修改程式碼時,這些型別檢查也能保持程式的穩定性,降低維護成本。

  5. 實際應用廣泛:不只在開發流程中有幫助,像是點餐系統、會員管理、商品查詢等都能應用這些型別檢查技術,提升使用體驗和系統的可靠性。


程式設計就像建築一座堅固的城堡,每一個嚴謹的型別都是你的磚瓦。
相信自己,寫出更強大、更安全的程式碼💻🚀
希望今天的文章可以幫助到你喔!٩(^ᴗ^)۶


上一篇
我推Day16 - 深度解析 TypeScript 泛型與動態服務系統
下一篇
我推Day18 - TypeScript 超能力解鎖:物件導向與模組的完美結合
系列文
我推的TypeScript 操作大全30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言