首页 > web前端 > css教程 > 揭开打字稿歧视工会的神秘面纱

揭开打字稿歧视工会的神秘面纱

Joseph Gordon-Levitt
发布: 2025-03-14 10:57:09
原创
960 人浏览过

Demystifying TypeScript Discriminated Unions

TypeScript是构建可扩展JavaScript应用的强大工具,几乎已成为大型Web JavaScript项目的标准。然而,对于新手来说,TypeScript也存在一些棘手之处,其中之一就是区分联合类型。

考虑以下代码:

interface Cat {
  weight: number;
  whiskers: number;
}
interface Dog {
  weight: number;
  friendly: boolean;
}
let animal: Dog | Cat;
登录后复制
登录后复制

许多开发者会惊讶地发现,访问animal时,只有weight属性是有效的,whiskersfriendly属性无效。本文将解释其中的原因。

在深入探讨之前,让我们快速回顾一下结构类型和名义类型,这将有助于理解TypeScript的区分联合类型。

结构类型

理解结构类型最好的方法是将其与名义类型进行对比。大多数你可能用过的类型化语言都是名义类型化的。例如C#代码(Java或C 类似):

class Foo {
  public int x;
}
class Blah {
  public int x;
}
登录后复制

即使FooBlah的结构完全相同,它们也不能相互赋值。以下代码:

Blah b = new Foo();
登录后复制

会产生以下错误:

<code>无法隐式转换类型“Foo”为“Blah”</code>
登录后复制

这些类的结构无关紧要。Foo类型的变量只能赋值给Foo类的实例(或其子类)。

TypeScript则相反,它采用结构类型。如果两个类型具有相同的结构,TypeScript就认为它们是兼容的。

因此,以下代码可以正常运行:

class Foo {
  x: number = 0;
}
class Blah {
  x: number = 0;
}
let f: Foo = new Blah();
let b: Blah = new Foo();
登录后复制

类型作为匹配值的集合

让我们进一步说明这一点。给定以下代码:

class Foo {
  x: number = 0;
}

let f: Foo;
登录后复制

f是一个变量,它可以保存任何与Foo类实例结构相同的对象,在本例中,这意味着一个表示数字的x属性。这意味着即使是一个普通的JavaScript对象也可以被接受。

let f: Foo;
f = {
  x: 0
};
登录后复制

联合类型

让我们回到本文开头的代码:

interface Cat {
  weight: number;
  whiskers: number;
}
interface Dog {
  weight: number;
  friendly: boolean;
}
登录后复制

我们知道:

let animal: Dog;
登录后复制

使animal成为任何与Dog接口结构相同的对象。那么以下代码是什么意思?

let animal: Dog | Cat;
登录后复制
登录后复制
登录后复制

这将animal类型定义为任何与Dog接口匹配的对象,或者任何与Cat接口匹配的对象。

那么为什么现在的animal只允许我们访问weight属性呢?简单来说,是因为TypeScript不知道它是什么类型。TypeScript知道animal必须是DogCat,但它可能是两者中的任何一个。如果允许我们访问friendly属性,但在实例中animal实际上是Cat而不是Dog,则可能会导致运行时错误。对于whiskers属性也是如此。

联合类型是有效值的联合,而不是属性的联合。开发者经常写这样的代码:

let animal: Dog | Cat;
登录后复制
登录后复制
登录后复制

并期望animal拥有DogCat属性的联合。但这又是一个错误。这指定animal具有一个值,该值与有效的Dog值和有效的Cat值的联合匹配。但是TypeScript只允许你访问它知道存在的属性。目前,这意味着联合中所有类型的属性。

类型收窄

现在,我们有:

let animal: Dog | Cat;
登录后复制
登录后复制
登录后复制

animalDog时,我们如何正确地将其视为Dog并访问Dog接口上的属性,当它是Cat时也同样处理?目前,我们可以使用in运算符。这是一个你可能不太常看到的旧式JavaScript运算符,它允许我们测试一个属性是否存在于一个对象中。例如:

let o = { a: 12 };

"a" in o; // true
"x" in o; // false
登录后复制

事实证明,TypeScript与in运算符深度集成。让我们看看如何使用:

let animal: Dog | Cat = {} as any;

if ("friendly" in animal) {
  console.log(animal.friendly);
} else {
  console.log(animal.whiskers);
}
登录后复制

这段代码不会产生错误。在if块内,TypeScript知道存在friendly属性,因此将animal转换为Dog。在else块内,TypeScript类似地将animal视为Cat。你甚至可以在代码编辑器中将鼠标悬停在这些块中的animal对象上查看这一点。

区分联合类型

你可能期望博文到此结束,但不幸的是,通过检查属性是否存在来收窄联合类型非常有限。它对我们简单的DogCat类型很有效,但是当我们有更多类型以及这些类型之间更多重叠时,事情很容易变得更复杂,也更脆弱。

这就是区分联合类型派上用场的地方。我们将保留之前的所有内容,只是向每种类型添加一个属性,其唯一作用是区分(或“区分”)这些类型:

interface Cat {
  weight: number;
  whiskers: number;
  ANIMAL_TYPE: "CAT";
}
interface Dog {
  weight: number;
  friendly: boolean;
  ANIMAL_TYPE: "DOG";
}
登录后复制

请注意两种类型上的ANIMAL_TYPE属性。不要将其误认为是具有两个不同值的字符串;这是一个字面量类型。ANIMAL_TYPE: "CAT";表示一个恰好包含字符串“CAT”的类型,仅此而已。

现在我们的检查变得更可靠:

let animal: Dog | Cat = {} as any;

if (animal.ANIMAL_TYPE === "DOG") {
  console.log(animal.friendly);
} else {
  console.log(animal.whiskers);
}
登录后复制

假设联合中参与的每个类型都具有ANIMAL_TYPE属性的不同值,则此检查将万无一失。

唯一的缺点是你现在需要处理一个新属性。每次创建DogCat的实例时,都必须为ANIMAL_TYPE提供唯一正确的数值。但不用担心忘记,因为TypeScript会提醒你。?

进一步阅读

如果你想了解更多信息,我建议阅读TypeScript文档关于类型收窄的部分。这将更深入地介绍我们这里讨论的内容。在该链接中有一个关于类型断言的部分。这些允许你定义你自己的自定义检查来收窄类型,而无需使用类型鉴别器,也无需依赖in关键字。

结论

在本文开头,我说过,在以下示例中,为什么weight是唯一可访问的属性:

interface Cat {
  weight: number;
  whiskers: number;
}
interface Dog {
  weight: number;
  friendly: boolean;
}
let animal: Dog | Cat;
登录后复制
登录后复制

我们学到的是,TypeScript只知道animal可以是DogCat,但不能同时是两者。因此,我们只能得到weight,它是两者之间唯一的公共属性。

区分联合类型的概念是TypeScript区分这些对象的方式,并且这种方式非常具有可扩展性,即使对于更大的对象集合也是如此。因此,我们必须在两种类型上创建一个新的ANIMAL_TYPE属性,该属性保存我们可以用来检查的单个字面量值。当然,这是另一件需要跟踪的事情,但它也产生了更可靠的结果——这正是我们首先从TypeScript想要得到的。

以上是揭开打字稿歧视工会的神秘面纱的详细内容。更多信息请关注PHP中文网其他相关文章!

本站声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
作者最新文章
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板