【问题标题】:TypeScript: Discriminated Unions with optional valuesTypeScript:具有可选值的可区分联合
【发布时间】:2017-01-05 21:53:01
【问题描述】:

给定以下类型:

interface FullName {
  fullName?: string
}

interface Name {
  firstName: string
  lastName: string
}

type Person = FullName | Name;

const p1: Person = {};
const p2: Person = { fullName: 'test' };
const p3: Person = { firstName: 'test' }; // Does not throw
const p4: Person = { badProp: true }; // Does throw, as badProp is not on FullName | Name;

我预计p3 会导致编译器错误,因为firstName 在没有lastName 的情况下存在,但它不会——这是错误还是预期?

此外,将 FullName.fullName 设为必需会导致 p3(和 p1)导致错误。

【问题讨论】:

  • 可能是一个单独的问题,但有没有办法强制执行可选联合?也就是说,假设FullName 确实需要fullName。有没有办法强制 Person 要求 (fullName) 或 (firstNamelastName) 或都不要求?

标签: typescript


【解决方案1】:

首先,您的界面FullName 确实只包含一个可选属性,这基本上使它匹配任何东西。然后当你用它做一个联合类型时,得到的类型将与所有东西兼容。

但是,考虑声明和分配字面量对象还有另一个问题,那就是您只能声明已知属性:Why am I getting an error "Object literal may only specify known properties"?

所以你可以毫无问题地做到这一点:

var test = { otherStuff: 23 };
const p4: Person = test;

但不是这个

const p4: Person = { otherStuff: 23 };

在你的情况下,firstNameFullName | Name 的已知属性,所以没关系。

正如@artem 回答的那样,discriminated unions 在打字稿中具有特殊含义,除了常规联合之外,需要特殊的结构假设。

【讨论】:

  • 我可能是错的,但它似乎不像具有可选属性的接口导致类型等于any。例如,如果是这种情况,设置 const p4: Person = { badProp: true } 不会导致错误?
  • @BenSouthgate 不,这不完全等同于我的任何糟糕措辞。
  • @BenSouthgate 并对此进行扩展:所有对象都匹配空类型 {},就像它们匹配只有一个可选属性的类型一样。然而,声明一个对象字面量是另一个问题。这里我们有另一个约束,那就是我们只能声明与声明的对象分配给的类型匹配的属性。它确实意味着该对象实际上并不满足目标类型。
  • 并且具有单个可选属性的类型与any 不等价,因为可以看出any 具有对象可以具有的所有可能属性,每个属性都是可选的。因此,如果您的联合类型包含any,则所有属性都可以在针对该联合类型的对象字面量中声明。对于具有唯一可选属性的类型,情况并非如此。虽然,他们似乎确实无视了仅在其中一个成分完全为空时才声明已知属性的规则。喜欢{}|Name
【解决方案2】:

您问题中的类型不是通常意义上的受歧视联合 - 您的联合成员没有称为 discriminant 的通用、非可选文字属性。

所以,正如@Alex 在他的回答中指出的那样,你的工会有点类似于

type Person = {
  fullName?: string
  firstName?: string
  lastName?: string
}

所以它可以用{ firstName: 'test' }初始化

使用真正的可区分联合,您可以返回检查非可选属性以及检查对象字面量是否只能指定已知属性的逻辑:

interface FullName {
  kind: 'fullname';  
  fullName?: string
}

interface Name {
  kind: 'name';  
  firstName: string
  lastName: string
}

type Person = FullName | Name;

const p1: Person = {kind: 'fullname'};  // ok
const p2: Person = {kind: 'fullname', fullName: 'test' };  // ok

检查非可选属性:

const p3: Person = {kind: 'name', firstName: 'test' }; 

错误:

Type '{ kind: "name"; firstName: string; }' is not assignable to type 'Person'.
  Type '{ kind: "name"; firstName: string; }' is not assignable to type 'Name'.
    Property 'lastName' is missing in type '{ kind: "name"; firstName: string; }'.

检查额外属性:

const p5: Person = { kind: 'fullname', bar: 42 }

错误:

Type '{ kind: "fullname"; bar: number; }' is not assignable to type 'Person'.
  Object literal may only specify known properties, and 'bar' does not exist in type 'Person'.

但是,正如@JeffMercado 发现的那样,类型检查仍然有点偏离:

const p6: Person = { kind: 'fullname', firstName: 42 };  // no error. why?

我会考虑为 typescript github 项目发布问题。

【讨论】:

  • 奇怪的是,这也不会报告原始错误:const p5: Person = { firstName: 42 };
  • 我想我知道为什么。您实际上并没有在这里利用有区别的联合模式。 kind 就像其他所有的属性一样,但它们在 FullName 和 Name 中都有不同的类型(字符串文字类型)。 p3 不起作用,因为它与任何一个成分都不兼容。 p5 不起作用,因为 bar 不是任何成分的已知属性。但是,p6 很好,因为它与 FullName 兼容,而 firstName 是其中一个成分的已知属性。
  • 要真正“激活”受歧视的联合,您必须像在您链接到的文档部分中那样输入保护它。首先,TS 会相应地缩小类型。
【解决方案3】:

2021 年更新:问题中的示例现在可以按预期工作。

至少因为 TypeScript 版本 3.3.3(目前可以在 TypeScript 操场上测试的最旧版本),所以您不需要判别式(即常见的、非可选的文字属性)。

给定

interface FullName {
  fullName?: string
}

interface Name {
  firstName: string
  lastName: string
}

type Person = FullName | Name;

和问题一样,下面的例子(被问这个问题的人标记为“不扔”)

const p3: Person = { firstName: 'test' }; // Does not throw

现在导致这个 TypeScript 错误:

Property 'lastName' is missing in type '{ firstName: string; }' but required in type 'Name'.

@artem 想知道为什么

const p6: Person = { kind: 'fullname', firstName: 42 };

在带有判别式 kind 的示例中不会引发错误。

好吧,因为最后是 TypeScript 版本 3.3.3,它确实会抛出一个错误,这正是预期的错误:

Object literal may only specify known properties, and 'firstName' does not exist in type 'FullName'.

请参阅this TypeScript playground,其中包括两个示例(有和没有歧视联合)。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-11-23
    • 2017-12-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-11-24
    • 2011-03-10
    相关资源
    最近更新 更多