不要使用这样的 TypeScript 类型。请使用 Map 模式
介绍
在实际项目中,我遇到了一个 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
是类型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),可以通过扩展添加新功能,而无需修改现有代码。
通过这种模式,我们可以轻松扩展反应列表而无需修改太多文件,同时仍然对可以添加的内容保持严格的控制。
代码?
您可以在此处访问存储库。
希望这个解决方案对你有帮助!感谢阅读。😊
在GitHub上关注我
文章来源:https://dev.to/perisicnikola37/dont-use-typescript-types-like-this-use-map-pattern-instead-ki3