標籤: 前端

  • 不要這樣使用 TypeScript 類型。改用 Map Pattern 吧。

    不要這樣使用 TypeScript 類型。改用 Map Pattern 吧。

    介紹

    在處理一個實際專案時,我遇到了一個特定的 TypeScript 實現,它雖然功能正常,但缺乏靈活性。在這篇部落格中,我將帶你了解我遇到的問題,以及如何通過使用Map 模式來改進設計,使其更具動態性。

    這個問題

    我遇到了這個 TypeScript 類型:

    // FinalResponse.ts
    import { Reaction } from './Reaction'
    
    export type FinalResponse = {
      totalScore: number
      headingsPenalty: number
      sentencesPenalty: number
      charactersPenalty: number
      wordsPenalty: number
      headings: string[]
      sentences: string[]
      words: string[]
      links: { href: string; text: string }[]
      exceeded: {
        exceededSentences: string[]
        repeatedWords: { word: string; count: number }[]
      }
      reactions: {
        likes: Reaction
        unicorns: Reaction
        explodingHeads: Reaction
        raisedHands: Reaction
        fire: Reaction
      }
    }

    此外,這個 Reaction 類型已被定義:

    // Reaction.ts
    export type Reaction = {
      count: number
      percentage: number
    }

    而這在一個函數中是這樣使用的:

    // calculator.ts
    export const calculateScore = (
      headings: string[],
      sentences: string[],
      words: string[],
      totalPostCharactersCount: number,
      links: { href: string; text: string }[],
      reactions: {
        likes: Reaction
        unicorns: Reaction
        explodingHeads: Reaction
        raisedHands: Reaction
        fire: Reaction
      },
    ): FinalResponse => {
      // Score calculation logic...
    }

    這種方法的問題

    現在,想像一下開發者需要添加新反應(例如,愛心、鼓掌等)的情境。
    鑑於目前的設置,他們必須:

    • 修改 FinalResponse.ts 檔案以新增反應類型。
    • 如有必要,請更新 Reaction.ts 類型。
    • 修改 calculateScore 函數以包含新的反應。
    • 可能需要更新依賴此結構的應用程式的其他部分。

    因此,他們不是在一個地方添加新的反應,而是最終在三個或更多文件中進行更改,這增加了錯誤和冗餘的可能性。這種方法是緊密耦合的

    解決方案

    我提出了一個更乾淨的解決方案,通過引入一個更靈活且可重用的結構。

    // FinalResponse.ts
    import { Reaction } from './Reaction'
    
    export type ReactionMap = Record<string, Reaction>
    
    export type FinalResponse = {
      totalScore: number
      headingsPenalty: number
      sentencesPenalty: number
      charactersPenalty: number
      wordsPenalty: number
      headings: string[]
      sentences: string[]
      words: string[]
      links: { href: string; text: string }[]
      exceeded: {
        exceededSentences: string[]
        repeatedWords: { word: string; count: number }[]
      }
      reactions: ReactionMap
    }

    解釋:

    • ReactionMap: 這個類型使用了 Record<string, Reaction>,這意味著任何字串都可以作為鍵,而值將始終是 Reaction 類型。
    • FinalResponse: 現在,FinalResponse 中的 reactions 欄位是 ReactionMap 類型,讓你可以動態添加任何反應,而無需修改多個文件。

    乾淨的程式碼

    在 calculator.ts 文件中,函數現在看起來像這樣:

    // calculator.ts
    export const calculateScore = (
      headings: string[],
      sentences: string[],
      words: string[],
      totalPostCharactersCount: number,
      links: { href: string; text: string }[],
      reactions: ReactionMap,
    ): FinalResponse => {
      // Score calculation logic...
    }

    但等等!我們需要一些控制

    雖然新的解決方案提供了靈活性,但它也帶來了添加未經檢查反應的風險,這意味著任何人都可能將任何字串作為反應添加。我們絕對不希望這樣。

    為了解決這個問題,我們可以對允許的反應實施更嚴格的控制。

    更安全的解決方案

    這是更新後的版本,我們將反應限制在預先定義的一組允許值中:

    // FinalResponse.ts
    import { Reaction } from './Reaction'
    
    type AllowedReactions =
      | 'likes'
      | 'unicorns'
      | 'explodingHeads'
      | 'raisedHands'
      | 'fire'
    
    export type ReactionMap = {
      [key in AllowedReactions]: Reaction
    }
    
    export type FinalResponse = {
      totalScore: number
      headingsPenalty: number
      sentencesPenalty: number
      charactersPenalty: number
      wordsPenalty: number
      headings: string[]
      sentences: string[]
      words: string[]
      links: { href: string; text: string }[]
      exceeded: {
        exceededSentences: string[]
        repeatedWords: { word: string; count: number }[]
      }
      reactions: ReactionMap
    }

    視覺化呈現

    結論

    這種方法在靈活性與控制之間取得了平衡:

    • 靈活性:你可以通過僅修改AllowedReactions類型來輕鬆添加新的反應。
    • 控制: 使用聯合類型確保只能使用允許的反應,防止添加無效或不需要的反應的風險。

    這段程式碼遵循了開放/封閉原則 (OCP),透過擴展來新增功能,而無需修改現有程式碼。

    使用這種模式,我們可以輕鬆擴展反應列表,而無需修改太多文件,同時仍然嚴格控制可以添加的內容。