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

在对象文字中赋值时,为什么TypeScript将字符串文字联合类型转换为字符串?,typescript,union-types,Typescript,Union Types,我喜欢TypeScript中的字符串文字联合类型。 我遇到了一个简单的例子,我希望联合类型被保留 以下是一个简单的版本: 设foo=false; 常数bar=foo?“foo’:‘bar’; 常数foobar={ 酒吧 } bar正确键入为'foo'|'bar': 但是foobar.bar被键入为string: 只是好奇为什么 更新 所以@jcalz和@ggradnig确实是很好的观点。但后来我意识到我的用例有一个额外的转折点: type Status='foo'|'bar'|'baz';

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

以下是一个简单的版本:

设foo=false;
常数bar=foo?“foo’:‘bar’;
常数foobar={
酒吧
}
bar
正确键入为
'foo'|'bar'

但是
foobar.bar
被键入为
string

只是好奇为什么

更新 所以@jcalz和@ggradnig确实是很好的观点。但后来我意识到我的用例有一个额外的转折点:

type Status='foo'|'bar'|'baz';
设foo=false;
常数条:状态=foo?'foo’:‘bar’;
常数foobar={
酒吧
}
有趣的是,
bar
确实有一种类型的
Status
。但是,
foobar.bar
的类型仍然是
“foo”|“bar”

看来要让它表现出我所期望的样子,唯一的办法就是将
'foo'
转换成
状态
,比如:

const bar=foo?““foo”作为状态:“bar”;

在这种情况下,键入操作确实正常。我同意。

那是因为TypeScript对
let
const
的处理方式不同。常量总是用“更窄”的类型处理——在本例中是文本。变量(和非只读对象属性)使用“加宽”类型-
字符串处理。这是文字类型附带的规则

现在,虽然您的第二个赋值可能是一个常量,但该常量的属性实际上是可变的-它是一个非只读属性。如果您不提供“上下文类型”,那么狭义的推断将丢失,您将得到更宽的
字符串
类型

。我可以引述:

没有类型注释的常量变量或只读属性的推断类型是初始值设定项的类型

对于带有初始值设定项且无类型注释的
let
变量、
var
变量、
参数
非只读属性
推断的类型是初始值设定项的加宽文本类型

更清楚的是:

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

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

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'

编译器使用一些启发式方法来确定。其中之一是:

  • 为对象文字中的属性推断的类型是表达式的加宽文字类型,除非该属性具有包含文字类型的上下文类型
因此,默认情况下,
“foo”|“bar”
会在分配给
foobar
的对象文本中加宽到
string


TS 3.4的更新 您现在可以使用来请求更窄的类型:

const foobar = {
    bar
} as const;
/* const foobar: {
    readonly bar: "foo" | "bar";
} */
本答案其余部分中的
字面意义上()
函数可能仍然有一些用处,但我建议尽可能使用
作为const


请注意启发式的一部分,即“除非属性具有包含文字类型的上下文类型”。提示编译器像
“foo”|“bar”
这样的类型应该保持狭窄的方法之一是让它与
字符串
(或包含它的联合)匹配类型

以下是我有时用来执行此操作的辅助函数:

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;
并且类型被推断为
{bar:“foo”|“bar”}
,正如您所期望的那样


无论您是否使用类似于
字面意义上的东西()
,我希望这对您有所帮助;祝你好运

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

type Status = 'foo' | 'bar' | 'baz';
let foo = false;
const bar: Status = foo ? 'foo' : 'bar';
条的声明类型是Status,但通过控制流分析,它的推断类型仍然缩小到三个可能值中的两个,
'foo'|'bar'

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

const zoo = bar; // const zoo: "foo" | "bar"
如果不将类型断言
作为状态
,就无法关闭基于控制流分析的类型推断,除非在需要的位置显式声明类型:

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