我喜欢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
:
但是'foo' | 'bar'
被键入为foobar.bar
:
好奇为什么。
所以@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
在这种情况下,键入确实可以正常工作。我可以。
答案 0 :(得分:7)
编译器使用一些启发式方法来确定when to widen literals。其中之一是以下内容:
- 为对象文字中的属性推断的类型是表达式的扩展文字类型,除非该属性具有包含文字类型的上下文类型。
因此,默认情况下,在您分配给"foo" | "bar"
的对象常量中,string
会扩展为foobar
。
请注意以下部分:“除非属性具有包含文字类型的上下文类型”。向编译器提示类似"foo" | "bar"
的类型应该缩小的一种方法是使它与constrained和string
(或包含它的并集)匹配。以下是我有时用来执行此操作的辅助函数:
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)
这是因为let
和const
的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
}