首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >当使用Exclude和Exclude时,判别器不按预期工作

当使用Exclude和Exclude时,判别器不按预期工作
EN

Stack Overflow用户
提问于 2020-04-17 01:52:15
回答 1查看 221关注 0票数 0

我不太会说这个问题,但我偶然发现了一个特殊的问题。我试图通过使用具有Thingy类型的foo属性来区分不同的Kind

代码语言:javascript
复制
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编译器抱怨:

代码语言:javascript
复制
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)
EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2020-04-17 03:56:54

一些事情导致了这种情况的发生。这里的主要问题是TypeScript不支持嵌套的受歧视的联合。微软/打字稿#18758有一个相当古老的开放建议;如果您非常关心这个问题,您可能想去那里给它一个或描述您的用例,如果它是特别有说服力的。不过,就目前而言,它并不是语言的一部分。这意味着,这样的代码会成功:

代码语言:javascript
复制
type Discrim = { a: 0, c: string } | { a: 1, c: number };
declare const d: Discrim;
d.a === 0 ? d.c.toUpperCase() : d.c.toFixed(); // okay

但是这样的代码失败了:

代码语言:javascript
复制
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开始,增加支持用于分配,如下所示:

代码语言:javascript
复制
const k: Kind = { kind: Math.random() < 0.5 ? "A" : Math.random() < 0.5 ? "B" : "C" }; // okay

分配给k的对象具有{kind: "A" | "B" | "C"}类型,该类型在技术上不能与受歧视的Kind联盟的任何成员分配,这是在TS3.4及以下版本中遇到的错误:

代码语言:javascript
复制
// Type '{ kind: "A" | "B" | "C"; }' is not assignable to type 'Kind'.

但在TS3.5及以上版本中,编译器会进行额外的检查,以获得具有联合类型判别属性的单个对象类型,并将该联合向上传播到具有单类型判别属性的对象类型的联合中。所以这是编译出来的。

不幸的是,正如我们所提到的,根据编译器,ExCThingy | CThingy不是一个受歧视的联合。上述支持只适用于受歧视的工会。对于不受歧视的工会,您将得到相同的错误:

代码语言:javascript
复制
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不被认为是受歧视的结合,所以{ foo: Kind, something: 'test', somethingelse: 'test2' }类型的值不被认为是ExCThingy | CThingy类型。

所以这就是现在发生的事。要在这里继续,您可能需要使用类型断言来告诉编译器,您确信您所做的工作是安全的:

代码语言:javascript
复制
fooArray.map(e => DoSomething({
    foo: e.foo,
    something: 'test',
    somethingelse: 'test2'
} as ExCThingy | CThingy)); // no error

或者将单个对象拆分为编译器可以实际检查的联合,如下所示:

代码语言:javascript
复制
fooArray.map(e => DoSomething(e.foo.kind === "C" ?
    { foo: e.foo, somethingelse: 'test2' } : { foo: e.foo, something: 'test' }
));

好吧,希望这能帮上忙,祝你好运!

操场链接到代码

票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/61262886

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档