交叉口没有正确缩小联合

时间:2018-06-08 09:12:03

标签: typescript

我有以下用例:我有一个Foo和Bar的联合。 Foo和Bar都有一个type属性,每个属性都有自己的枚举值" foo" &" bar"。此外,它们每个都有一个有效负载属性,Foo是一个字符串而Bar是一个数字。

enum Types {
    FOO = "foo",
    BAR = "bar"
}

interface Foo {
    type: Types.FOO;
    payload: string;
}

interface Bar {
    type: Types.BAR;
    payload: number;
}

type Union = Foo | Bar;

现在,我尝试使用交叉点缩小联合,我在此定义类型:

// Does not pass type check because payload must be a string
const baz: Union & { type: Types.FOO } = {
  type: Types.FOO,
  payload: 42
}

这不会传递类型检查(正如我所料),因为有效负载是一个数字,但当类型为FOO时应该是一个字符串。

另一方面,这编译(正如我所料):

// Passes type check
const baz: Union & {type: Types.FOO} = {
    type: Types.FOO,
    payload: "foobar"
}

现在我有一个函数handleFoo,我这样输入:

function handleFoo(foo: Union & {type: Types.FOO}) {
    // payload is string | number
    console.log(foo.payload)
    if (foo.type === Types.FOO) {
        // payload is still string | number??
        console.log(foo.payload)
    }
}

TS告诉我有效负载是一个字符串|号码联盟。但与此同时,这种类型无法构建,有效载荷是一个数字。另外,即使我检查类型是FOO,它仍然不会缩小foo的类型。

另一方面,如果我这样做,我会在FOO检查后得到预期的缩小。

function handleFoo2(foo: Union) {
    console.log(foo.payload) // payload is string | number
    if (foo.type === Types.FOO) {
        // payload is string
        console.log(foo.payload)
    }
}

第二种方法(以及handleFoo中的常规类型检查)对我来说是不切实际的,因为该检查已经在handleFoo的调用者中执行。

这是一个错误,还是仅仅是对类型缩小的某种误解?

有没有办法解决这个问题,或者我应该向TypeScript团队提交错误?

2 个答案:

答案 0 :(得分:0)

您从哪里得到的(我一直无法弄清楚)?

  

TS告诉我有效负载是一个字符串|数字联盟。但是同时,以有效载荷为数字无法构造该类型。

我将您的代码放在一起,当我检查可以分配给按类型过滤的各个函数的有效载荷的类型时,最终结果是我使用了谨慎的“字符串”或“数字”类型,无法获取“字符串|数字”。

因此,如果您想要的是“联合”类型的通用处理程序,只要它们与有效载荷的类型无关,Typescript就会在您的代码上警告您是否有不兼容的类型。请注意,我添加了一个没有检查的函数(有点像不带if检查的handleFoo2),并且只要声明了一致(即您不将对象Foo与数字有效载荷或带有字符串有效载荷的Bar对象。

我整理了一个可以在Typescript Playground上运行的示例,其中所有功能都起作用。为了方便起见,我还复制了以下源代码。 Typescript Playground Example

enum Types {
    FOO = "foo",
    BAR = "bar"
}

interface Foo {
    type: Types.FOO;
    payload: string;
}

interface Bar {
    type: Types.BAR;
    payload: number;
}

type Union = Foo | Bar;


const bar: Union & { type: Types.BAR } = {
    type: Types.BAR,
    payload: 5
};


const foo: Union & { type: Types.FOO } = {
    type: Types.FOO,
    payload: "foobar"
};

const fooUnion: Union = {
    type: Types.FOO,
    payload: "foobar"
}

const barUnion: Union = {
    type: Types.BAR,
    payload: 5
}

function handleFoo(foo: Union & {type: Types.FOO}) {
    alert(typeof foo.payload);
    // alert displays "string"
    if (foo.type === Types.FOO) {
        alert(foo.payload);
    }
}

function handleFoo2(foo: Union) {
    alert(typeof foo.payload);
    // alert displays "string"
    if (foo.type === Types.FOO) {
        alert(foo.payload)
    }
}

function handleUnion(union: Union) {
    alert(typeof union.payload);
    alert(union.payload);
}

function handleBar(foo: Union & {type: Types.BAR}) {
    alert(typeof foo.payload);
    // alert displays "number"
    if (foo.type === Types.BAR) {
        alert(foo.payload);
    }
}

function handleBar2(foo: Union) {
    alert(typeof foo.payload);
    // alert displays "number"
    if (foo.type === Types.FOO) {
        alert(foo.payload)
    }
}

handleFoo(foo);
handleFoo2(foo);
handleBar(bar);
handleBar2(bar);

handleFoo(fooUnion);
handleFoo2(fooUnion);
handleBar(barUnion);
handleBar2(barUnion);

handleUnion(fooUnion);
handleUnion(barUnion);

答案 1 :(得分:0)

尝试用Extract<Union, {type: Types.FOO}>代替Union & { type: Types.FOO }