[TS] Enhancing React Views with Enums

在設計元件時,面對不同的情境通常會需要撰寫各種不同的表達式。一般除了使用物件來定義之外,Enums 的寫法也是一種既優雅又高效的選擇。
Enum
Enum 是在 TypeScript 中的語法,稱作「列舉」
在種類上也區分了 Numeric、String、Heterogeneous、Const、Reverse,基本用法可以參考 文件,這邊就不贅述了。
通常 Enum 在實作上會用來定義一組具「Immutability(不可變性)」且有意義的「集合」,例如:狀態、角色、事件等等。
而 Enums 的最大優勢就是在他的「可讀性」與「維護性」,假設今天有個情境需要同時 handle 不同角色下的權限,以及對應的事件…
你可能會使用物件表達的方式來設計,但物件是可變的,當今天在很多不同模組下去使用時,有可能因為一些限制必須在某個地方去做修改,這個物件也會變得越來越不穩定。
而相較之下,Enum 則能提供一種更穩定且結構化的方式來管理這類不可變的值。
優點:
- Enums 具不可變性,定義後就不能被修改,因此從根本上避免了值被意外改動的風險。
- TypeScript 的型別檢查能確保 Enum 的值只能取自預定義的範圍,減少意外錯誤。
- Enums 提供語意化的名稱,讓程式碼更直觀,便於理解和維護。
缺點:
- 使用一般的 Enums 時,Compile 後的程式碼會包含相關的映射,可能導致代碼體積略微增加(const enum 可解決此問題)。
- Enums 是靜態的,無法在執行時動態新增或修改值,不適合頻繁變動的場景。
使用情境
直接看例子:
假設你正在開發一個電商的結帳系統,這個系統需要根據使用者選擇的配送方式顯示不同的表單頁面。
最簡單無腦的寫法,你也可以用 switch case,但實際上差不多,雖然還是好閱讀的,但程式碼攏長,且太多重複性的結構,未來也不好在上面加以擴充。
export const Checkout = () => { const [currentStep, setCurrentStep] = useState('selectDelivery');
const renderStep = () => { if (currentStep === 'selectDelivery') { return <SelectDelivery />; } else if (currentStep === 'deliveryAddress') { return <DeliveryAddress />; } else if (currentStep === 'sevenStore') { return <SevenStore />; } else if (currentStep === 'reviewOrder') { return <ReviewOrder />; } return null; };
return <div>{renderStep()}</div>;};
進一步優化成物件表達的方式:
export const Checkout = () => { const [currentStep, setCurrentStep] = useState('selectDelivery');
const stepComponents: Record<string, JSX.Element> = { selectDelivery: <SelectDelivery />, deliveryAddress: <DeliveryAddress />, sevenStore: <SevenStore />, reviewOrder: <ReviewOrder />, };
return <div>{stepComponents[currentStep]}</div>;};
相較之下,已經省去攏長的 if else,程式碼也更簡潔,也更易於擴充,符合 clean code。
但要注意物件具傳參考的特性,如果未來沒有好好維護的話,有可能會在其他模組被誤用,影響原始物件。
而 TypeScript Enum 的型別安全性以及鍵值的不可變性,直接從根本上解決了誤用的問題。
使用 Enum 重構:
export enum CheckoutStep { SelectDelivery = 'selectDelivery', DeliveryAddress = 'deliveryAddress', SevenStore = 'sevenStore', ReviewOrder = 'reviewOrder'}
// 對應的元件const stepComponents: Record<CheckoutStep, JSX.Element> = { [CheckoutStep.SelectDelivery]: <SelectDelivery />, [CheckoutStep.DeliveryAddress]: <DeliveryAddress />, [CheckoutStep.SevenStore]: <SevenStore />, [CheckoutStep.ReviewOrder]: <ReviewOrder />,};
export const Checkout = () => { const [currentStep, setCurrentStep] = useState<CheckoutStep>(CheckoutStep.SelectDelivery); // 當取得對應的 key 後直接 render 對應的元件 return <div>{stepComponents[currentStep]}</div>;};
且你也能在元件中去傳遞 props,然後在個別元件中去做些操作。
總結
原始 if-else 寫法 | 使用物件映射方法 | 使用 Enum 優化後 | |
---|---|---|---|
可讀性 | if-else 結構較攏長 | 利用物件簡化結構,但每個人寫法風格不同,可讀性差異大 | 利用 Record 將結構扁平化,簡潔易讀 |
型別安全性 | 任意字串均可作為鍵值 | 任意字串作為鍵值,無類型安全保護 | 受 Enum 約束,防止不合法的值傳入 |
擴充性 | 每次新增都需手動修改多處邏輯 | 每次新增只需在物件中新增一個鍵值對 | Enum 與對應物件讓新增步驟變得簡單 |
程式碼結構 | 重複的 if-else 結構 | 無重複結構,但需要手動處理邏輯映射 | 使用列舉和映射物件統一管理邏輯 |
維護性 | 隨著需求變動,if-else 需要修改多處 | 修改時需注意物件的管理,可能造成誤用 | Enum 與映射物件管理,修改時只需更新 Enum |
參考來源
Licensed under CC BY-SA 4.0
Unless otherwise noted, content on this site is licensed under CC BY-SA 4.0. You are free to share and adapt with attribution.