今天,我們要來聊聊如何從 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 幫你檢查資料的安全性啦!
假設系統不做任何檢查,開發者輸入了 'blue' 這種不存在的顏色,這可能會導致應用出現非預期的行為,比如錯誤的燈號顯示甚至崩潰。
回到我們早前的問題裡,我們知道在 findName
裡,我們應該只接受清單中的姓氏,而不是任意的字串。
要做到這一點,我們可以使用 TypeScript 的泛型(Generics
)來創建一個自訂型別,叫做 ExtractSurname
,用來提取名字清單中的合法姓氏。
先看這段程式碼,它看起來可能有點複雜,但別擔心,我會一步一步幫你解釋清楚:
type ExtractSurname<T extends string> =
T extends `${infer Firstname} ${infer Surname}` ? Surname : null;
這裡的邏輯可以想像成在一張票券系統裡,我們只會承認真實存在的姓氏,像是票券的「票號」,而不會承認那些不存在的票號。
ExtractSurname
接收一個字串型別 T
,這個型別代表任何字串。
然後我們用 TypeScript 的三元運算符來比較型別。
簡單來說,我們知道名字的格式是「 姓 名 」(Firstname Surname),因此在這裡,我們使用 infer
關鍵字,來推斷並提取出 Firstname
和 Surname
。
Surname
。null
。例如,你有一個購物網站,它裡面有很多商品分類。
你可能只想允許系統接受現有的商品分類來進行查詢,而不是允許隨便亂打的分類名稱。
這樣,我們可以確保查詢的效率和安全性。
回到我們的名字範例,現在我們有了一個 ExtractSurname
型別,接下來我們需要創建一個 Surname
型別,代表 names
陣列中的所有合法姓氏:
const names = ["王小明", "陳大文", "李小華"] as const;
type Surname = ExtractSurname<(typeof names)[number]>;
我們使用 as const
,讓 names
的型別變成「字面值型別」,這代表每個元素的型別都會是固定的字串,而不再只是一般的 string
。
然後 (typeof names)[number]
是一個巧妙的方式,表示我們要取 names
陣列中的每個元素的型別。
最終,我們得到了這個型別:
type Surname = "王" | "陳" | "李";
findName
函式最後一步就是更新我們的 findName
函式,確保它只接受合法的姓氏。
const findNameUsingSurname = (surname: Surname) => {
return names.find((name) => name.includes(surname))!;
};
// 現在只有 "王", "陳", "李" 是合法的輸入
const fullName = findNameUsingSurname("王");
console.log(fullName); // 輸出: 王小明
這樣一來,當你不小心輸入錯誤或不存在的值時,TypeScript 編譯器會幫你報錯!
再也不用擔心拼錯字導致奇怪的錯誤啦~
想像一下,當你在點餐時,系統只能接受菜單上已有的餐點名稱。
如果你不小心點了個不存在的「麻辣冰淇淋」,系統馬上提醒你這是無效的點單。
同樣地,在程式碼裡,我們也想要確保使用者輸入的資料是正確的,而不是隨便輸入。
無論是會員卡號還是點餐系統,這些實際應用都在提醒我們,資料驗證與型別檢查在程式設計中有多重要!
型別安全:透過 TypeScript 的型別限制,保證只接受合法的輸入,避免不必要的錯誤發生。
可讀性高:嚴格的型別約束讓程式碼更易於理解,團隊合作時也更容易上手。
早期錯誤檢測:在編譯時期就能發現輸入錯誤,大幅減少執行時才發現問題的風險。
提升維護性:即使在未來擴展或修改程式碼時,這些型別檢查也能保持程式的穩定性,降低維護成本。
實際應用廣泛:不只在開發流程中有幫助,像是點餐系統、會員管理、商品查詢等都能應用這些型別檢查技術,提升使用體驗和系統的可靠性。
程式設計就像建築一座堅固的城堡,每一個嚴謹的型別都是你的磚瓦。
相信自己,寫出更強大、更安全的程式碼💻🚀
希望今天的文章可以幫助到你喔!٩(^ᴗ^)۶