TypeScript 編程最佳實踐
本文介紹 TypeScript 編程的最佳實踐。
本指南說明如何運用 TypeScript 的型別來減少錯誤並寫出更易讀的程式碼的實用最佳實踐。
YouTube Video
TypeScript 編程最佳實踐
TypeScript 最大的優點是「透過型別來防止錯誤並使程式碼意圖明確」。
最佳實踐不僅僅是規則,而是用於撰寫安全、可讀且易於維護程式碼的一套原則。以下將結合實際範例,介紹常見的 TypeScript 最佳實踐。
避免使用 any,並盡量賦予型別有意義的定義
首先,讓我們來看看“避免使用 any 並賦予有意義的型別”這一點。
any 會完全停用型別檢查,這會讓使用 TypeScript 的意義喪失。不要為了讓程式暫時執行而使用 any,而應該盡可能詳細定義型別。
1// Bad
2function parse(data: any) {
3 return data.value;
4}這段程式碼可以接受任何值,因此無法防止執行時錯誤。
1// Good
2type ParsedData = {
3 value: string;
4};
5
6function parse(data: ParsedData): string {
7 return data.value;
8}透過定義型別,可以明確表示輸入和輸出的意圖,並提升安全性。
請務必使用 type 或 interface 明確定義物件結構。
接下來,讓我們來看看“始終使用 type 或 interface 明確定義物件結構”這一點。
若臨時使用物件,其結構容易變得模糊不清。請務必將型別拆出以提升重用性與維護性。
1// Bad
2function createUser(user: { name: string; age: number }) {
3 console.log(user.name);
4}即使是在小段程式碼中,也要養成分離型別的習慣是很重要的。
1// Good
2type User = {
3 name: string;
4 age: number;
5};
6
7function createUser(user: User): void {
8 console.log(user.name);
9}為型別命名可以讓整體程式碼的理解更加容易。
利用聯合型別精確表達所有可能的狀態。
如果用原始的 string 或 number 型別作為條件,容易出現未預期的值。透過使用聯合型別,你可以在型別層級上只表示允許的狀態。
1// Bad
2function setStatus(status: string) {
3 console.log(status);
4}在這段程式碼中,無意義的字串或錯誤的值無法在編譯階段被檢測出來。
1// Good
2type Status = "idle" | "loading" | "success" | "error";
3
4function setStatus(status: Status): void {
5 console.log(status);
6}透過使用聯合型別,你可以在編譯階段可靠地消除「不可能的狀態」。因此,條件分支的安全性與程式碼的可靠性都會提升。
請明確處理 null 與 undefined。
然後,讓我們來看看“明確處理 null 和 undefined”這一點。
在 TypeScript 中,用型別表示值可能不存在是很重要的。如果保持模糊不明,容易導致執行時錯誤。
1type User = {
2 name: string;
3 email?: string;
4};email 有可能不存在,因此應以此為前提處理。
1function printEmail(user: User): void {
2 if (user.email) {
3 console.log(user.email);
4 }
5}在使用可選值前務必進行檢查。
僅在不得已時才使用型別斷言 (as)。
再來,讓我們來看看“不過度使用型別斷言”這一點。
型別斷言會暫時略過 TypeScript 的型別檢查,來宣告「我知道這個值是這個型別」。過度使用會破壞型別安全性。
1// Bad
2const value = input as string;在這段程式碼中,即使實際值不是字串,也不會出現錯誤,這可能會導致執行階段錯誤。如下列程式碼所示,請優先選擇使用型別保護進行安全檢查。
1// Good
2function isString(value: unknown): value is string {
3 return typeof value === "string";
4}
5
6if (isString(input)) {
7 console.log(input.toUpperCase());
8}型別保護是一種能在檢查實際值時安全地判斷型別的機制。將型別保護優先於型別斷言,可以更容易地防止執行階段錯誤。
請勿過度依賴回傳值型別的型別推論。
接著,讓我們來看看“不過度依賴型別推斷來決定回傳型別”這一點。
雖然 TypeScript 的型別推論很強大,但對公開函式,明確標註回傳型別更安全。這樣可以將未來變更造成的影響降到最低。
1// Bad
2function sum(a: number, b: number) {
3 return a + b;
4}即使是小函式,也要明確表達用途與意圖。
1// Good
2function sum(a: number, b: number): number {
3 return a + b;
4}明確標示回傳型別能提升 API 的穩定性。
請用 unknown 安全處理輸入。
此外,讓我們來看看“使用 unknown 安全地接收外部輸入”這一點。
對於如 API、JSON 或使用者輸入等外部輸入,請使用 unknown 而非 any。這麼做可以確保所有值都經過驗證,維持型別安全。
1// Bad
2function handleResponse(data: any) {
3 console.log(data.id);
4}以下是如何利用 unknown 驗證型別。
1// Good
2function handleResponse(data: unknown): void {
3 if (
4 typeof data === "object" &&
5 data !== null &&
6 "id" in data
7 ) {
8 console.log((data as { id: number }).id);
9 }
10}unknown 不能直接使用;這是一種需要驗證的型別。在處理外部輸入時特別有效。
透過組合小型型別提升表達能力。
還有,讓我們來看看“透過組合小型別來提升表現力”這一點。
一次定義大型型別會降低可讀性與可維護性。請將型別拆分為有意義的小單元,並在需要時組合。
1type Id = number;
2
3type UserProfile = {
4 id: Id;
5 name: string;
6};
7
8type UserWithStatus = UserProfile & {
9 status: "active" | "inactive";
10};將型別當作元件般組合有助於結構化設計。
type 與 interface
interface 的優點
type 與 interface 雖然都能定義型別,但用途與特性有所不同。根據用途區分使用,可以使型別定義更容易傳達意圖。
1// Bad
2type User = {
3 id: number;
4 name: string;
5};
6
7type AdminUser = {
8 id: number;
9 name: string;
10 role: "admin";
11};像這樣重複共通部分,會讓程式碼容易對變更產生脆弱性。
1// Good
2interface User {
3 id: number;
4 name: string;
5}
6
7interface AdminUser extends User {
8 role: "admin";
9}interface 適合需要擴充(extends)的設計,也適用於表達物件「形狀」。
type 的優點
另一方面,type 更具有表達力,適合用於聯合型別與交叉型別。
1// Good
2type Status = "idle" | "loading" | "success" | "error";
3
4type ApiResponse<T> =
5 | { status: "success"; data: T }
6 | { status: "error"; message: string };type 很適合用於表達狀態、選項或各種組合。
選擇 type 或 interface 的指引
一般建議:物件結構與契約請使用 interface,有聯合、交叉、型別運算等需求時請使用 type。
兩者有許多相似之處,但選用時仍應考量哪一種型別更能清楚傳達其存在理由。
請將型別視為文件。
最後,讓我們來看看“將型別作為文件來書寫”這一點。
良好的型別定義能傳達比註解更充分的資訊。重要的是要以“僅透過型別就能理解規格”的狀態為目標。
1type ApiError = {
2 code: number;
3 message: string;
4 retryable: boolean;
5};如此一來,TypeScript 的一大優勢就是型別定義能夠作為一種規格文件來發揮作用。
總結
TypeScript 的最佳實踐重點並非過度嚴苛。本質在於透過型別明確表達意圖,並寫出容易因應變動的程式碼。
在日常開發累積這些小原則,長期下來就能達成「更容易審查」、「錯誤更少」、「讓自己與他人未來更好理解程式碼」等目標。
請先思考「這樣可以用型別怎麼表達?」,你就能寫出高品質、充分發揮 TypeScript 優勢的程式碼。
您可以在我們的 YouTube 頻道上使用 Visual Studio Code 來跟隨上述文章一起學習。 請也查看我們的 YouTube 頻道。