如何避免折叠包装的联合体类型

时间:2019-06-27 13:10:09

标签: typescript union-types

在我看来,当Typescript碰巧知道初始值时,它正在折叠一个包装的联合类型(不需要)。我不确定这是一个错误,但是我想知道是否有避免该错误的方法。

一个例子:

type Prime = 3|5|7;
type OrDonuts<T> = T | 'donuts';

function compare<T>(a: T, b: OrDonuts<T>) {
    return a == b;
}

let value: Prime;
compare(value, 3);
// OK!

value = 5;
compare(value, 3);
// TS2345: Argument of type '5' is not assignable to parameter of type 'OrDonuts<3>'

为了解决此错误,我必须明确地说出value = 5 as Prime;之类的内容。

这是错误,是预期的行为,我只是做错了吗?

(节点:10.15,打字稿:3.5.1)

2 个答案:

答案 0 :(得分:3)

TypeScript类型检查器执行control flow type analysis,这意味着它会努力确定运行时变量内部的值会发生什么,并将缩小这些变量的类型以使其匹配。发生这种情况的一种特定方式是,如果您拥有union type的变量或属性,并且编译器看到您为其分配了更具体类型的值,它将把变量的类型缩小为更具体的类型。类型,至少直到您为变量分配了其他值为止。

这通常是理想的,因为它支持以下用例:

let x: number | string = Math.random() < 0.5 ? "hello" : "goodbye"; // x is string now
if (x.length < 6) { // no error here
  x = 0; // the widest x can be is string | number, so this assignment is fine
}

请注意,即使您已经注释 xnumber | string,但编译器仍知道在初始分配后肯定是string。因此,当您检查x.length时它不会抱怨(就像x可能是number一样)。这是一个有用的行为,禁用它会导致大量实际的TypeScript代码损坏。

不幸的是,它也对您在此处看到的行为负责。在将5分配给value之后,编译器将value视为包含缩小类型5的变量。不是Prime。您可以始终将value扩展到Prime,但是编译器不会自动执行。它认为通过警告您正在呼叫compare(5 as 5, 3)是被禁止的,这对您有帮助。

在这种情况下,如您所见,覆盖此行为的唯一方法是使用type assertion。您可以在初始分配中或在对compare()的调用中进行以下声明:

let value2: Prime = 5 as Prime
compare(value2, 3); // okay

let value3: Prime = 5;
compare(value3 as Prime, 3); // okay

或者,您可以在对T的调用中手动指定通用类型compare(),这也可以:

let value4: Prime = 5;
compare<Prime>(value4, 3); // okay

您可以使用所有这些选项。

为此,我能找到的最规范的文档来源是Microsoft/TypeScript#8513,尤其是this comment

好的,希望能有所帮助;祝你好运!

Link to code

答案 1 :(得分:1)

这是Union Types的预期行为。通过声明

type Prime = 3|5|7; // it is either 3 OR 5 OR 7 but never all of them at the the same time

您告诉ts编译器Prime是值之一。现在,您将5分配给键入字段value

value = 5; // type Prime is now 5
compare(value, 3); // <T> is inferred by the ts-compiler as 5

要解决此问题,您必须使用value:Prime或像以前一样键入assert。


无需类型推断,您就可以通过value,例如

value = 5;
compare<2>(value, 3); // Argument of type '5' is not assignable to parameter of type '2'.

或者您是否愿意以更通用的方式满足compare函数参数

let test = 5;
compare(test, 3); // <T> is now number which 5 and 3 are.