我有一个歧视联盟,简化为这个例子。
type FoobarEvent = FooEvent | BarEvent;
interface FooEvent {
type: 'foo';
foo: {
name: string;
}
}
interface BarEvent {
type: 'bar';
bar: {
id: string;
}
}
当然我可以检查判别式并适当缩小联合类型的范围。
// let's assume this exists
declare const foobarEvent: FoobarEvent;
let fooName: string | undefined;
if (foobarEvent.type === 'foo') {
fooName = foobarEvent.foo.name;
}
但是我想避免这个 if 块,所以我尝试使用可选链接来缩小类型范围,但这不起作用,因为其中一个联合体没有我要访问的字段。
const fooNameByChaining = foobarEvent.foo?.name;
类型“FoobarEvent”上不存在属性“foo”。
类型“BarEvent”上不存在属性“foo”。
所以显然缩小范围需要在可选链接之前发生。这是 TypeScript 类型系统的限制还是可以将其作为一项功能添加到 Typescript 中?或者还有另一种我不知道的解决方法吗?
如果 TypeScript 允许您在任何类型上附加
.foo?.name
,那么每当我们使用 ?.
运算符时,它都会失败类型检查。
例如,我的
foobarEvent
是一种类型,其中 foo
是可选属性:
type FoobarEvent = {
id: number;
bar: string;
foo?: {
name: string;
}
}
在这种情况下我的意图是正确的:
const name = foobarEvent.foo?.name;
或者 - 我是否通过将
.foo?.name
添加到错误的类型而造成拼写错误?
const name = localStorage.foo?.name;
如果这可行的话,它实际上不是类型安全的。由于子类型,
BarEvent
包含 foo
字段是合法的。
此代码与您的代码相结合将导致
fooName
具有类型 number
,这不是 string | undefined
的有效子类型:
fooBarEvent: FooBarEvent = {
type: 'bar',
foo: {
name: 5
},
bar: {
name: 'myBar'
}
};
另请参阅关于 TypeScript 问题跟踪器上的可选链和联合的讨论中的此评论。那里还列出了一些可能的解决方法。这是一种相当简单且可靠的解决方法(来源):
type FoobarEvent = FooEvent | BarEvent;
interface FooEvent {
type: 'foo';
foo: {
name: string;
}
bar?: never;
}
interface BarEvent {
type: 'bar';
// This 'never' ensures that an absence of foo means that we have a FooEvent
foo?: never;
bar: {
id: string;
}
}
// This automatically infers the correct type now
const func = (foobarEvent: FoobarEvent): string | undefined => foobarEvent.foo?.name;