发布于 2026-01-06 0 阅读
0

使用 TypeScript ADT 编写更可靠的 React DEV 全球展示挑战赛,由 Mux 呈现:展示你的项目!

使用 TypeScript ADT 编写更可靠的 React 代码

由 Mux 赞助的 DEV 全球展示挑战赛:展示你的项目!

您可能以前听说过代数数据类型(ADT),但并不了解它们如何应用于日常代码——因此本文将提供一些示例和解释,说明为什么您应该开始使用它们。

在深入了解 ADT 之前,让我们先来了解一下 ADT 的基本组成。

基本类型

在 JavaScript 中,你不能声明一个类型来阻止其他类型被赋值。下面的例子表明,任何类型都可以赋值给language变量。如果我们之后需要,可以给它赋值 `a` number、`b`boolean或 `c` object,但如果我们预期变量类型不是字符串,这样做可能会导致将来出现 bug。

let language = 'en'
Enter fullscreen mode Exit fullscreen mode

在 TypeScript 中,我们可以更好地控制类型声明。如下所示,现在我们只能将 `a` 赋值stringlanguage变量,这要好得多。这样,以后当我们访问这个变量时,就可以相当肯定它的值是一个字符串,并据此进行相应的操作。

let language: string = 'en'
Enter fullscreen mode Exit fullscreen mode

但我们可以做得更好……

联合类型

使用 TypeScript 联合类型,我们可以说一个对象可以有多种类型。😮 在下面的例子中,我们可以看到language变量可以是 `a`string或 `b` number

let language: string | number = 'en'
Enter fullscreen mode Exit fullscreen mode

你可能会想,“酷,但是为什么要在一个变量中使用两种不同的类型呢?”

这是一个很好的问题,但在弄清楚为什么需要这样做之前,我们需要了解在 TypeScript 中,任何事物都可以被视为一种类型,包括特定的字符串值。

现在我们可以精确地指定哪些值可以赋给这个language变量。

let language: 'en' | 'fr' | 'ru' = 'en'
Enter fullscreen mode Exit fullscreen mode

现在我们只能给它赋予某些language

现在有趣的问题是,“我们如何知道当前存储的是哪种类型?”

如果一个变量可以保存两种不同类型的值,那么在访问该值时,必须先检查其类型,然后再对其进行操作。

let nameOrID: string | number = 'Jason'

if (typeof nameOrID === 'string') {
  // do something with string...
} else if (typeof nameOrID === 'number') {
  // do something with number...
}
Enter fullscreen mode Exit fullscreen mode

这一点很重要,因为如果我们不检查值类型,我们就无法知道当前使用的是哪种类型——所以我们可能会尝试对字符串进行数学运算,或者对数字进行字符串操作……

了解了这些之后,我们现在可以解释 Typescript 的可区分联合类型了

受歧视的工会类型

利用我们所学到的关于联合类型的知识,我们可以构造一个遵循某些规则的特殊联合体。

应遵守的规则如下:

  1. 联合体中的所有类型都具有一个共同的属性。
  2. 需要声明一个由这些类型组成的联合类型。
  3. 公共区域必须配备保安人员。

以下是一个例子:

type HockeyGame = {
  kind: 'hockey' // Rule 1 - common property 'kind'
  homeScore: number
  awayScore: number
  clock: number
  isDone: boolean
}

type BaseballGame = {
  kind: 'baseball' // Rule 1 - common property 'kind'
  inning: number
  isTop: boolean
  stadium: string
}

// Rule 2 - Union type declared
type Game = HockeyGame | BaseballGame

const gameToString = (game: Game): string => {
  // Rule 3 - Type guard on the common property
  switch (game.kind) {
    case 'hockey':
      return `Hockey game clock: ${game.clock.toString()}`
    case 'baseball':
      const frame = game.isTop ? 'top' : 'bottom'
      return `Baseball game is in the ${frame} of inning ${game.inning}`
  }
}
Enter fullscreen mode Exit fullscreen mode

在上面的例子中,我们运用了所学的知识,即通过 `kind`kind属性将特定字符串分配给某个类型。`kind` 属性只能是`or` 或hockey`or` baseball,而不能是其他任何值。

该公共属性充当对象的 ID,使我们能够知道定义了哪些其他属性以及可以访问哪些其他属性。

遵循这些规则可以让 TypeScript 编译器知道哪些字段可用。因此,如果您已经检查过守卫规则并认为它有效,hockey那么编译器将只允许您访问该HockeyGame类型中的字段。

undefined这样可以避免因访问可能在不同时间存在也可能不存在的属性而导致的许多错误。

使用 React 的 ADT

现在让我们看看如何在 React 中利用这种模式。

利用上面声明的游戏类型,我们可以根据联合体中的公共属性安全地渲染不同的组件。

const HockeyGameBox = ({ game }: { game: HockeyGame }) => (
  <div>
    {game.homeScore} - {game.awayScore}
  </div>
)

const BaseballGameBox = ({ game }: { game: BaseballGame }) => (
  <div>
    {game.inning} - {game.stadium}
  </div>
)

const renderGame = (game: Game) => {
  switch (game.kind) {
    case 'hockey':
      return <HockeyGameBox game={game} />
    case 'baseball':
      return <BaseballGameBox game={game} />
  }
}

const GamePage = () => {
  const [games] = useState<Game[]>([
    /* mix of different games */
  ])
  return games.map(renderGame)
}
Enter fullscreen mode Exit fullscreen mode

如您所见,使用抽象数据类型 (ADT) 可以大大减少使用动态数据时出现的运行时错误。它并非完全杜绝错误,但却是朝着正确方向迈出的一步。

要了解更多关于 ADT 的信息,请查看我在 Elm 中发布的相关文章:Elm 的 Javascript 远程数据类型

文章来源:https://dev.to/rametta/using-typescript-adt-s-to-write-more-reliable-react-2o43