发布于 2025-12-10 0 阅读
0

不要使用这样的 TypeScript 类型。请使用 Map 模式

不要使用这样的 TypeScript 类型。请使用 Map 模式

介绍

在实际项目中,我遇到了一个 TypeScript 实现,它虽然功能齐全,但缺乏灵活性。在这篇博客中,我将带您了解我遇到的问题,以及如何通过使用Map 模式实现更动态的方法来改进设计。

目录

  1. 问题
  2. 这种方法的问题
  3. 解决方案
  4. 干净的代码
  5. 更安全的解决方案
  6. 视觉表现
  7. 结论

问题

我遇到了这种 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
  }
}
Enter fullscreen mode Exit fullscreen mode

此外,该Reaction类型定义如下:

// Reaction.ts
export type Reaction = {
  count: number
  percentage: number
}
Enter fullscreen mode Exit fullscreen mode

这在如下函数中使用:

// 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...
}
Enter fullscreen mode Exit fullscreen mode

这种方法的问题

现在,想象一下开发者需要添加新的反应(例如,心动、鼓掌等)的场景。
在当前设置下,他们需要:

  • 修改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
}
Enter fullscreen mode Exit fullscreen mode

解释:

  • ReactionMap:此类型使用Record<string, Reaction>,这意味着任何字符串都可以作为键,并且值始终是类型Reaction
  • FinalResponse:现在,中的反应字段FinalResponse是类型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...
}
Enter fullscreen mode Exit fullscreen mode

可是等等!我们需要一些控制

虽然新的解决方案提供了灵活性,但也带来了添加未经检查的反应的风险,这意味着任何人都可能将任意字符串添加为反应。我们绝对不希望出现这种情况。

为了解决这个问题,我们可以对允许的反应实施更严格的控制。

更安全的解决方案

这是更新后的版本,我们将反应限制为一组预定义的允许值:

// 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
}
Enter fullscreen mode Exit fullscreen mode

视觉表现

TypeScript 类型

TypeScript 类型

结论

这种方法在灵活性和控制力之间取得了平衡:

  • 灵活性:您只需修改AllowedReactions类型即可轻松添加新的反应。
  • 控制:使用联合类型可确保只能使用允许的反应,从而防止添加无效或不需要的反应的风险。

该代码遵循开放/封闭原则 (OCP),可以通过扩展添加新功能,而无需修改现有代码。

通过这种模式,我们可以轻松扩展反应列表而无需修改太多文件,同时仍然对可以添加的内容保持严格的控制。

代码?

您可以在此处访问存储库


希望这个解决方案对你有帮助!感谢阅读。😊

在GitHub上关注我


文章来源:https://dev.to/perisicnikola37/dont-use-typescript-types-like-this-use-map-pattern-instead-ki3