英文:
Why doesn't Typescript give an error on this discriminating union return type
问题
I'm curious why typescript is not giving an error for the return type of the test1
function, whereas for the test2
function it is.
I would assume typescript is capable of determining the wrong return value for test1
as it can for test2
.
interface A {
status: 'a'
data: {
value: string
}
}
interface B {
status: 'b'
}
type AB = A | B
function test1(arg: 'foo' | 'bar'): AB {
// this doesn't give a TS error, where it is actually wrong
return {
status: arg === 'foo' ? 'b' : 'a',
data: {
value: 'hello'
}
}
}
function test2(arg: 'foo' | 'bar'): AB {
if(arg === 'foo') {
return {
status: 'a',
data: {
value: 'hello'
}
}
}
// This does give an error (which I would expect)
return {
status: 'b',
data: {
value: 'hello'
}
}
}
Update:
If this is "by design" as @wonderflame mentions in the comment would there be an alternative way where you don't have to explicitly return each type?
英文:
I'm curious why typescript is not giving an error for the return type of the test1
function, whereas for the test2
function it is.
I would assume typescript is capable of determining the wrong return value for test1
as it can for test2
.
interface A {
status: 'a'
data: {
value: string
}
}
interface B {
status: 'b'
}
type AB = A | B
function test1(arg: 'foo' | 'bar'): AB {
// this doesn't give a TS error, where it is actually wrong
return {
status: arg === 'foo' ? 'b' : 'a',
data: {
value: 'hello'
}
}
}
function test2(arg: 'foo' | 'bar'): AB {
if(arg === 'foo') {
return {
status: 'a',
data: {
value: 'hello`'
}
}
}
// This does give an error (which I would expect)
return {
status: 'b',
data: {
value: 'hello'
}
}
}
Update:
If this is "by design" as @wonderflame mentions in the comment would there be an alternative way where you don't have to explicitly return each type?
答案1
得分: 1
TypeScript 的对象类型是开放和可扩展的,而不是封闭或精确(使用 Flow 的术语,并按照 microsoft/TypeScript#12936 中的要求)。由于 B
的定义完全没有提到 data
属性,这意味着 B
可能包含或不包含 data
属性。它不要求有一个,但也不禁止有一个。
这被 TypeScript 的某些情况下会执行多余属性检查所复杂化,但该功能更像是一个代码检查警告,旨在防止您设置编译器可能会跟踪丢失的属性。它实际上并不是用来使对象类型封闭或精确的。
多余属性检查通常会对辨别联合产生影响,但在 test1
中将 status
属性本身设为联合类型足以混淆事情,以至于它不会触发。再次强调,您不应该依赖多余属性检查来确保正确性。
如果您希望 B
一直禁止 data
属性,您需要明确更改 B
的定义以表示这一点。嗯,TypeScript 实际上不会让您禁止属性。您能够做到的最接近的方法是将其设为可选属性,并将其类型定义为不可能的 never
类型。这意味着该属性要么缺失,要么存在但没有定义的值(根据您的编译器选项,这可能包括 undefined
;请参阅 --exactOptionalPropertyTypes
以获取更多信息)。
像这样:
interface B {
status: 'b'
data?: never; // <-- B 上没有 data
}
现在事情开始按您的预期方式运作:
function test1(arg: 'foo' | 'bar'): AB {
return { // 错误!
status: arg === 'foo' ? 'b' : 'a',
data: {
value: 'hello'
}
}
}
英文:
TypeScript's object types are open and extendible, not sealed or exact (using Flow terminology, and as requested in microsoft/TypeScript#12936). Since B
's definition makes no mention whatsoever of the data
property, it means that a B
may or may not contain a data
property. It doesn't require one, but it doesn't prohibit one either.
This is complicated by the fact that TypeScript will sometimes perform excess property checking on object literals, but that feature is more of a linter warning and is meant to prevent you from setting properties the compiler will probably lose track of. It is not really meant to make object types sealed or exact.
Excess property checking does usually kick in for discriminated unions, but your approach in test1
of making the status
property itself a union confused things enough to not have it activate. Again, you're not really supposed to rely on excess property checking for correctness.
If you want B
to consistently prohibit a data
property, you will need to change B
's definition to say that explicitly. Well, TypeScript doesn't actually let you prohibit a property. The closest you can get is to make it an optional property of a type like the impossible never
type. This means the property will either be missing, or present without a defined value (depending on your compiler options that could include undefined
; see `--exactOptionalPropertyTypes for more info).
Like this:
interface B {
status: 'b'
data?: never; // <-- no data on B
}
And now things start behaving the way you expect:
function test1(arg: 'foo' | 'bar'): AB {
return { // error!
status: arg === 'foo' ? 'b' : 'a',
data: {
value: 'hello'
}
}
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论