网站搜索

typescript - 在后续的 Array.prototype.map 中类型缩小丢失


有人能帮忙解释以下类型检查错误吗:

interface Foo {
    kind: 'foo';
    a: string;
}

interface Bar {
    kind: 'bar';
    b: number;
}

type FooBar = Foo | Bar;

interface Container {
    ids: number[];
    fooBar: FooBar;
}

const cont: Container = {
    ids: [1, 2],
    fooBar: { kind: 'foo', a: 'a' },
};

switch (cont.fooBar.kind) {
    case 'foo':
        console.log(cont.fooBar.a); // OK

        cont.ids.map((id) => {
            console.log(`${id} - ${cont.fooBar.a}`);
            // Property 'a' does not exist on type FooBar
            //   Property 'a' does not exist on type Bar
        })
        break;
    case 'bar':
        console.log(cont.fooBar.b); // OK

        cont.ids.map((id) => {
            console.log(`${id} - ${cont.fooBar.b}`);
            // Property 'b' does not exist on type FooBar
            //   Property 'b' does not exist on type Foo
        })
        break;
}

操场上的现场示例。

答案#1

你可以通过将 fooBar 拿到一个本地的 const 来修复它,并在 switch 中使用 fooBar 而不是 cont.fooBar:

const { fooBar } = cont; // <====
switch (fooBar.kind) {
    case 'foo':
        console.log(fooBar.a); // OK

        cont.ids.map((id) => {
            console.log(`${id} - ${fooBar.a}`); // OK now
        })
        break;
    case 'bar':
        console.log(fooBar.b); // OK

        cont.ids.map((id) => {
            console.log(`${id} - ${fooBar.b}`); // OK now
        })
        break;
}

实时复制

最初我在每个case中都这样做了,但 TypeScript 对上述写法也能接受。

我认为这是因为 TypeScript 无法确定回调函数是同步的,并且由于如果回调是异步的,cont.fooBar 可能会被其他代码更改,因此它无法确定缩小后的类型仍然是正确的。将其抓取到本地可以消除这种疑虑。这个理论得到了这样的事实的支持:如果你把它写成 let { fooBar }=cont; 而不是 const { fooBar }=cont;,它又无法保持缩小后的类型。