在对象文字中进行赋值时,为什么TypeScript将字符串文字联合类型转换为字符串?

时间:2019-01-23 18:59:51

标签: typescript union-types

我喜欢TypeScript中的字符串文字联合类型。 我遇到了一个简单的案例,当时我希望保留联合类型。

这是一个简单的版本:

    Caused by: com.ibm.db2.jcc.am.SqlException: [jcc][10389][12245][4.25.13] 
Failure in loading native library db2jcct2, java.lang.UnsatisfiedLinkError: 
no db2jcct2 in java.library.path:  ERRORCODE=-4472, SQLSTATE=null

let foo = false; const bar = foo ? 'foo' : 'bar'; const foobar = { bar } 的正确键入为bar

enter image description here

但是'foo' | 'bar'被键入为foobar.bar

enter image description here

好奇为什么。

更新

所以@jcalz和@ggradnig确实很不错。但是后来我意识到我的用例有一个额外的变化:

string

有趣的是,type Status = 'foo' | 'bar' | 'baz'; let foo = false; const bar: Status = foo ? 'foo' : 'bar'; const foobar = { bar } 的类型为bar。但是Status的类型仍然是foobar.bar

似乎使它按我期望的方式运行的唯一方法是像这样将'foo' | 'bar'强制转换为'foo'

Status

在这种情况下,键入确实可以正常工作。我可以。

3 个答案:

答案 0 :(得分:7)

编译器使用一些启发式方法来确定when to widen literals。其中之一是以下内容:

  
      
  • 为对象文字中的属性推断的类型是表达式的扩展文字类型,除非该属性具有包含文字类型的上下文类型。
  •   

因此,默认情况下,在您分配给"foo" | "bar"的对象常量中,string会扩展为foobar

请注意以下部分:“除非属性具有包含文字类型的上下文类型”。向编译器提示类似"foo" | "bar"的类型应该缩小的一种方法是使它与constrainedstring(或包含它的并集)匹配。以下是我有时用来执行此操作的辅助函数:

type Narrowable = string | number | boolean | symbol | object |
  null | undefined | void | ((...args: any[]) => any) | {};

const literally = <
  T extends V | Array<V | T> | { [k: string]: V | T },
  V extends Narrowable
>(t: T) => t;

literally()函数仅返回其参数,但类型趋于更窄。是的,这很丑...我将其保存在实用程序库中。

现在您可以这样做:

const foobar = literally({
  bar
});

,并且按预期将类型推断为{ bar: "foo" | "bar" }

无论您是否使用类似literally()之类的东西,希望对您有所帮助。祝你好运!

答案 1 :(得分:3)

这是因为letconst的TypeScript处理方式不同。常量始终使用“更窄”的类型-在这种情况下为文字。变量(和非只读对象属性)使用“扩展”类型-string处理。这是文字类型附带的规则。

现在,尽管您的第二个分配可能是一个常量,但该常量的属性实际上是可变的-这是一个非只读属性。如果您不提供“上下文类型”,则窄的推断将丢失,而您将得到宽的string类型。

Here you can read more about literal types。我可以引用:

  

从const变量或只读属性(没有类型注释)中推断出的类型就是初始化程序的类型。

     

使用初始化程序为let变量,var变量,parameter non-readonly property 推断的类型,并且没有类型注释扩展了初始化程序的文字类型。

更清楚地是:

  

为对象文字中的属性推断的类型是表达式的扩展文字类型,除非该属性具有包含文字类型的上下文类型。

顺便说一句,如果您提供常量的上下文类型,则该类型将传递给变量:

const bar = foo ? 'foo' : 'bar';
let xyz = bar // xyz will be string

const bar: 'foo' | 'bar' = foo ? 'foo' : 'bar';
let xyz = bar // xyz will be 'foo' | 'bar'

答案 2 :(得分:3)

使用三值文字联合类型回答更新的问题:

type Status = 'foo' | 'bar' | 'baz';
let foo = false;
const bar: Status = foo ? 'foo' : 'bar';

声明的bar类型是状态,但是通过控制流分析,它的推断类型仍缩小到'foo' | 'bar'中只有两个可能的值。

如果您声明另一个没有类型的变量,TypeScript将为bar使用推断的类型,而不是声明的类型:

const zoo = bar; // const zoo: "foo" | "bar"

没有依靠类型断言as Status,除了在需要的地方明确声明类型之外,没有其他方法可以基于控制流分析来关闭类型推断:

const foobar: {bar: Status} = {
    bar // has Status type now
}