使用 Exclude 和 | 时鉴别器未按预期工作

我不太知道如何表达这个问题,但我偶然发现了一个特殊的问题。我试图Thingy通过使用foo类型为Kind.


interface AKind {

    kind: 'A';

}


interface BKind {

    kind: 'B';

}


interface CKind {

    kind: 'C';

}


type Kind = AKind | BKind | CKind;


type ExCThingy = {

    foo: Exclude<Kind, CKind>,

    something: 'test';

}


type CThingy = {

    foo: CKind,

    somethingelse: 'test2';

}


type Foo = {

    foo: Kind

}


function DoSomething(args: ExCThingy | CThingy) {

    function isCThingy(args: ExCThingy | CThingy): args is CThingy {

        return args.foo.kind === 'C';

    }


    if (isCThingy(args)) {

        //Do C thing

        return;

    }


    //else Do ExC thing

    return;

}


const a: AKind = {

    kind: 'A',

}


const b: BKind = {

    kind: 'B',

}


const c: CKind = {

    kind: 'C',

}



const fooArray: Foo[] = [{

    foo: a,

},

{

    foo: b,

},

{

    foo: c,

    }];


fooArray.map(e => DoSomething({

    foo: e.foo,

    something: 'test',

    somethingelse: 'test2'

}));

ts 编译器抱怨:


Argument of type '{ foo: Kind; something: "test"; somethingelse: "test2"; }' is not assignable to parameter of type 'ExCThingy | CThingy'.

  Type '{ foo: Kind; something: "test"; somethingelse: "test2"; }' is not assignable to type 'CThingy'.

    Types of property 'foo' are incompatible.

      Type 'Kind' is not assignable to type 'CKind'.

        Type 'AKind' is not assignable to type 'CKind'.

          Types of property 'kind' are incompatible.

            Type '"A"' is not assignable to type '"C"'.(2345)


杨__羊羊
浏览 159回答 1
1回答

翻阅古今

有几件事导致这种情况发生。这里的主要问题是 TypeScript 不支持嵌套的可区分联合。在microsoft/TypeScript#18758有一个相当古老的开放建议;如果您对此非常关心,您可能想去那里给它一个👍,或者如果它特别引人注目,请描述您的用例。不过,就目前而言,它还不是语言的一部分。这意味着,这样的代码会成功:type Discrim = { a: 0, c: string } | { a: 1, c: number };declare const d: Discrim;d.a === 0 ? d.c.toUpperCase() : d.c.toFixed(); // okay但是这样的代码失败了:type NestedDiscrim = { a: { b: 0 }, c: string } | { a: { b: 1 }, c: number };declare const n: NestedDiscrim;n.a.b === 0 ? n.c.toUpperCase() : n.c.toFixed(); // error!因为在前者中,Discrim编译器将其视为带有a属性作为判别式的可区分联合,但在后者中,NestedDiscrim不将其视为带有a属性作为判别式的可区分联合。同样,在您的情况下,Kind是受歧视的工会,但ExCThingy | CThingy不是。对于编译器视为可区分联合的类型,从 TypeScript 3.5 开始,添加了对如下赋值的支持:const k: Kind = { kind: Math.random() < 0.5 ? "A" : Math.random() < 0.5 ? "B" : "C" }; // okay分配给的对象的k类型{kind: "A" | "B" | "C"}在技术上不可分配给受歧视联合的任何单个成员Kind,这是您在 TS3.4 及以下版本中遇到的错误:// Type '{ kind: "A" | "B" | "C"; }' is not assignable to type 'Kind'.但是在 TS3.5 及更高版本中,编译器会进行额外的检查以获取具有联合类型判别属性的单个对象类型,并将联合向上传播到具有单类型判别属性的对象类型的联合中。这样就可以编译了。不幸的是,正如我们所提到的,ExCThingy | CThingy根据编译器,它不是一个有区别的联合。上述支持仅适用于受歧视的工会。对于非歧视工会,你会得到同样的错误:type NotDiscrim = { a: string } | { a: number };const x: NotDiscrim = { a: Math.random() < 0.5 ? "" : 1 }; // error in all versions of TS// Type '{ a: string | number; }' is not assignable to type 'NotDiscrim'编译器根本不对非区分联合执行联合传播分析。并且由于ExCThingy | CThingy不被认为是有区别的联合,因此 type 的值{ foo: Kind, something: 'test', somethingelse: 'test2' }不被认为是 type ExCThingy | CThingy。所以这就是正在发生的事情。要在此处继续,您可能需要使用类型断言来告诉编译器您确定您正在做的事情是安全的:fooArray.map(e => DoSomething({&nbsp; &nbsp; foo: e.foo,&nbsp; &nbsp; something: 'test',&nbsp; &nbsp; somethingelse: 'test2'} as ExCThingy | CThingy)); // no error要么,要么将单个对象拆分为编译器可以实际检查的联合,如下所示:fooArray.map(e => DoSomething(e.foo.kind === "C" ?&nbsp; &nbsp; { foo: e.foo, somethingelse: 'test2' } : { foo: e.foo, something: 'test' }));
打开App,查看更多内容
随时随地看视频慕课网APP

相关分类

JavaScript