TypeScript联合类型不会抛出错误。

3

那么,类型定义:

// type definitions

class GenericDto {
  public ids: string[] = [];
  public dateFrom: string | null = null;
  public dateTo: string | null = null;
}

class FleetInformationDto extends GenericDto { }
class VehicleInformationDto extends GenericDto { }

enum ReportQueueAction {
  GENERATE
}

enum ReportQueueType {
  VEHICLE_INFORMATION,
  FLEET_INFORMATION
}

type ReportQueue = {
  action: ReportQueueAction;
  type: ReportQueueType.FLEET_INFORMATION;
  dto: FleetInformationDto
} | {
  action: ReportQueueAction,
  type: ReportQueueType.VEHICLE_INFORMATION,
  dto: VehicleInformationDto;
}

实现和执行:

// implementation
const dto: FleetInformationDto = {
  ids: ["1", "2"],
  dateFrom: '2021-01-01',
  dateTo: '2021-02-01'
}

const queueData: ReportQueue = {
  action: ReportQueueAction.GENERATE,
  type: ReportQueueType.FLEET_INFORMATION,
  dto: dto
}

// ^ works as expected

但是如果我们将 "VehicleInformationDto" 添加到类型 FLEET_INFORMATION 中,它将不会抛出错误。

const dto2: VehicleInformationDto = {
  ids: ["1", "2"],
  dateFrom: '2021-01-01',
  dateTo: '2021-02-01'
}

const queueData2: ReportQueue = {
  action: ReportQueueAction.GENERATE,
  type: ReportQueueType.FLEET_INFORMATION,
  dto: dto2 // <-- no error thrown here
}

好的,那么这里有什么问题吗?我有什么遗漏吗?

问题是:为什么我可以在 queueData2 中将 VehicleInformationDto 分配给 dto,而 TypeScript 却希望它是 FleetInformationDto

编辑:好的,是的,那是因为它们共享相同的属性,那么我怎么添加检查呢?

在线编辑器


@MikeS. 可以在playground链接中看到,typescript期望队列数据2中的dto为FleetInformationDto类型,但是当你将VehicleInformationDto类型分配给它时,它不会抛出错误。为什么我能够分配期望FleetInformationDto的变量VehicleInformationDto呢? - Ali Demirci
1
您的沙盒链接很遗憾没有显示任何内容,但是您的问题是VehicleInformationDto可分配给FleetInformationDto,因为它具有完全相同的属性。 - user13258211
1
进一步说,TypeScript 是结构化类型,而不是名义上类型。对于 TypeScript 来说,type A = { foo: number }type B = { foo: number } 是相同的类型。在这里查看更多信息 - Jared Smith
1
是的,您可以为联合类型中的每个类型添加一个称为“鉴别器”的唯一属性。 - Jared Smith
2
虽然不是完全相同的问题,但这个答案应该能帮到你解决这个问题 - Jared Smith
显示剩余4条评论
1个回答

1
Typescript是结构类型化的,而不是名义类型化的。这意味着在Typescript看来,以下两种类型相同structurally typed, not nominally typed

class FleetInformationDto extends GenericDto { }
class VehicleInformationDto extends GenericDto { }

虽然我认为这是在像Javascript这样的对象是属性抓包的语言中添加静态类型的绝对正确的选择,但它可能会导致一些微妙的陷阱:
interface Vec2 {
  x: number
  y: number
}

interface Vec3 {
  x: number
  y: number
  z: number
}

const m = { x: 0, y: 0, z: "hello world" };
const n: Vec2 = m; // N.B. structurally m qualifies as Vec2!
function f(x: Vec2 | Vec3) {
  if (x.z) return x.z.toFixed(2); // This fails if z is not a number!
}
f(n); // compiler must allow this call

我们正在进行一些图形编程,并使用2D和3D向量,但是我们有一个问题:对象可以具有额外的属性,仍然符合结构上的要求,这导致联合类型出现问题(听起来很熟悉吗?)。
在您的特定情况下,答案是使用鉴别器轻松区分联合中相似的类型:
interface FleetInformationDto extends GenericDto {
    // N.B., fleet is a literal *type*, not a string literal
    // *value*.
    kind: 'fleet'
}

interface VehicleInformationDto extends GenericDto {
    kind: 'vehicle'
}

这里我使用了字符串,但任何唯一的编译时常量(任何基本值或枚举成员)都可以。另外,由于您没有实例化类并且仅将它们用作类型,我将它们设置为接口,但是相同的原则适用。
"

Playground

" 可以翻译为 "

{{链接1:游乐场}}

"。
现在你可以清楚地看到错误,即类型“fleet”无法分配给类型“vehicle”。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接