Typescript联合类型一致性

4
拥有一个 Typescript 的联合类型变量 A
type A = {
    b: true
    x: number
  } | {
    b: false
    x: string
  }

declare const v: A

我可以通过使用 if 判别块来检查属性 b 的值类型,以正确地将属性 x 分配给正确的类型,以保持 A 类型的一致性。
if (v.b) {  // v.x is number

  // ok for compiler 
  v.x = 3   

  //  compiler error as v.x should be number 
  v.x = ''

} else { // v.x is string

  //  compiler error as v.x should be string 
  v.x = 3   

  // ok for compiler 
  v.x = ''  
}

在判别式块之外,v.x 显示为 number | string,但编译器没有抱怨将 x 分配给 number | string,尽管这会破坏 type A 的一致性。
v.x = 3   // ok for compiler 
v.x = ''  // ok for compiler 

有没有一种方法可以强制编译器拒绝这个?
在typescriptlang.org/play上查看

1
虽然我认为这是一个有趣的问题,但同时它也感觉像是一个XY问题,所以如果您提供一些动机或背景信息,可能会更有用。也许有一个更合适的语法可以使用。 - apokryfos
1
您可以使用通用类型别名: https://www.typescriptlang.org/docs/handbook/advanced-types.html - Oleg
@apokryfos 这是一个普遍性问题,开发人员只需仔细检查鉴别属性即可简单解决...
关键在于我希望TS编译器在类型明确定义后,在编译时保持类型的一致性,毕竟这是Typescript的主要目的之一...
显而易见的情况是:一个开发团队正在处理涉及“类型A”的代码库,我希望尽可能地减少运行时错误
我只是想知道是否有一种方法可以避免这个问题,或者它是Typescript功能上的缺陷。
- aleclofabbro
1
我的意思是,microsoft/TypeScript#9825讨论了 TypeScript 不安全行为的一般情况以及为什么无法消除,但我不能确定它是否与这个特定实例相关。 - jcalz
一般来说,一个“健全”的类型系统只会让你做类型安全的事情,而一个“完备”的类型系统只会禁止你做类型不安全的事情。TypeScript的类型系统既不健全(允许你做一些不安全的事情),也不完备(阻止你做一些安全的事情)。在这种情况下,即使不安全,编译器也没有阻止你分配给v.x...因此它是不健全的一个例子。 - jcalz
显示剩余3条评论
2个回答

1

好的,我认为我已经找到了关于这个问题的规范GitHub问题:microsoft/TypeScript#14150,建议“不应允许不安全的类型不兼容赋值”。截至2019年9月13日,它仍然是一个未解决的问题,标记为“等待更多反馈”,因此,如果您认为自己有一个令人信服的用例尚未在其中提到,您可能需要在那里发表评论。我不会抱着等待实施这一点的希望,因为相关问题,如通过标志强制执行只读严格性启用方差注释要么已关闭,要么还没有得到处理。

这里的问题涉及到类型系统缺乏“声音”。一个声音的类型系统只会让你做安全的事情。但是在这里,它允许你对可能违反对象声明类型的对象进行属性赋值。这种不安全的宽容意味着类型系统是“不完备的”。单独考虑这一点并不被认为是一个错误。“应用一个声音或‘可证明正确’的类型系统”不是TypeScript的设计目标之一。正确性和生产力之间存在权衡,修复此问题可能比其价值更麻烦。有关TypeScript的完整性和/或缺失的更多讨论,请参见microsoft/TypeScript#9825
这里的问题在于:编译器假设将同一类型写入属性是安全的,而你可以从中读取。但通常情况下并非如此,正如你的示例和链接问题中的相关示例所示。
interface A { kind: "A"; foo(): void; }
interface B { kind: "B"; bar(): void; }

function setKindToB(x: A | B): void {
    x.kind = "B"; // clearly unsafe
}

所以该怎么办呢?不确定。TypeScript 3.5引入了一个更改,用于索引访问写入(例如foo[bar] = baz),因此如果键是联合类型(比如barMath.random()<0.5 ? "a" : "b"),则必须将属性类型的交集写入它,而不是联合(因此baz的类型必须是typeof foo.a & typeof foo.b,不能再接受typeof foo.a | typeof foo.b)。这是一个可靠性改进,禁止了一些以前允许的无效操作。它也禁止了许多以前允许的有效操作。很多人仍然对此感到不满,并且新的问题仍然经常被提出。我想如果他们解决了这个问题,这里也会出现同样的问题……你会得到你期望的错误,并且许多代码库将会崩溃。目前我建议你最好避免进行这些赋值操作,我理解这并没有什么安慰作用。

无论如何,希望这些信息对你有所帮助。祝你好运!


谢谢你提供有用的深入解释。这让我对TS有了更好的理解和洞察力。 - aleclofabbro

0

1
很不幸,那不再是一个带标签联合体,甚至也不是联合体了。我猜这里的使用情况是变量必须是联合类型,并且无法通过注解来缩小范围。如果我错了,@aleclofabbro可以纠正我。 - jcalz
Oleg,正如@jcalz所指出的,你的用例是不同的,根本不涉及联合类型。 - aleclofabbro

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