是否可以通过可选链接缩小联合类型的范围?

问题描述 投票:0回答:2

我有一个歧视联盟,简化为这个例子。

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
2个回答
2
投票

如果 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;

0
投票

如果这可行的话,它实际上不是类型安全的。由于子类型,

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;

打字稿游乐场

© www.soinside.com 2019 - 2024. All rights reserved.