简体   繁体   English

将兼容类型分配给可区分的联合类型时出错

[英]Error when assigning compatible type to a discriminated union type

I have a reduced example case.我有一个简化的示例案例。 I create a discriminated union AOrB , and a (to my mind) compatible type C :我创建了一个有区别的联合AOrB和一个(在我看来)兼容的类型C

interface A {
  kind: 'a';
}

interface B {
  kind: 'b';
}

type Kind = 'a' | 'b';

interface C {
  kind: Kind;
}

interface D {
  kind: 'b';
}

type AOrB = A | B;

function test(input: C): AOrB {
  return input;
}

function test2(input: D): AOrB {
  return input;
}

Fails with失败

Type 'C' is not assignable to type 'AOrB'.
  Type 'C' is not assignable to type 'B'.
    Types of property 'kind' are incompatible.
      Type 'Kind' is not assignable to type '"b"'.
        Type '"a"' is not assignable to type '"b"'.

This makes it seem like I can't assign an object to a variable of type AOrB unless it is known ahead of time whether it is, in fact, and A or a B .这使得我似乎无法将对象分配给AOrB类型的变量,除非提前知道它实际上是A还是B It isn't just that they need the same exact type, as test2 compiles fine.这不仅仅是因为它们需要相同的确切类型,因为test2编译得很好。

Can anyone explain what is going on?任何人都可以解释发生了什么?

UPDATE: 2019-05-30 with the release of TypeScript 3.5 this should be addressed by smarter union type checking .更新:2019-05-30 随着 TypeScript 3.5 的发布,这应该通过更智能的联合类型检查来解决。 The following applies to 3.4 and below:以下适用于 3.4 及以下:


This issue has been reported before and it's essentially a compiler limitation.这个问题之前已经报告过,它本质上是一个编译器限制。 While you and I understand that {a: X} | {a: Y}虽然你我都明白{a: X} | {a: Y} {a: X} | {a: Y} is equivalent to {a: X | Y} {a: X} | {a: Y}等价于{a: X | Y} {a: X | Y} , the compiler does not. {a: X | Y} ,编译器没有。

In general you can't combine unions of objects into objects of unions unless the objects differ only in the type of one property (or rather that the differing properties represent all possible distributions of the unions).通常,您不能将对象的联合组合成联合对象,除非这些对象仅在一个属性的类型上有所不同(或者不同的属性代表联合的所有可能分布)。 For example, you can't collapse {a: X, b: Z} | {a: Y, b: W}例如,您不能折叠{a: X, b: Z} | {a: Y, b: W} {a: X, b: Z} | {a: Y, b: W} to something like {a: X | Y, b: Z | W} {a: X, b: Z} | {a: Y, b: W}类似于{a: X | Y, b: Z | W} {a: X | Y, b: Z | W} {a: X | Y, b: Z | W} ... the type {a: X, b: W} is assignable to the latter but not the former. {a: X | Y, b: Z | W} ... {a: X, b: W}类型可分配给后者但不能分配给前者。

Imagine writing the compiler to try to make such reductions;想象一下编写编译器来尝试进行这样的缩减; for the vast majority of cases it would spend precious processor time examining union types only to find that it cannot combine them.对于绝大多数情况,它会花费宝贵的处理器时间来检查联合类型,却发现它无法组合它们。 The relatively rare cases like yours where the reduction is possible are probably just not worth it.像您这样可以减少的相对罕见的情况可能只是不值得。

Even in your case, I'm skeptical;即使在你的情况下,我也持怀疑态度。 are you really trying to make a discriminated union where only the discriminant property is different?你真的想建立一个只有判别属性不同的判别联合吗? That's a pathological case.这是一个病态的案例。 The intended use case for discriminated unions is to take a set of different types and add a single property to let you know which type a value is.可区分联合的预期用例是采用一组不同的类型并添加一个属性来让您知道值是哪种类型。 As soon as you start adding other properties which make the discriminated types truly different, you would have to abandon the idea of collapsing the union.一旦您开始添加其他属性,使可区分类型真正不同,您将不得不放弃折叠联合的想法。


So, assuming you really need the non-discriminated discriminated union above, what can you do?那么,假设你真的需要上面的非歧视联合,你能做什么? The easiest way is to just tell the compiler you know what you're doing by using a type assertion :最简单的方法是使用类型断言告诉编译器你知道你在做什么:

function test(input: C): AOrB {
  return input as AOrB; // you know best
}

This now compiles, although it's not really safe:现在可以编译了,尽管它不是很安全:

function test(input: C): AOrB {
  return (Math.random() < 0.5 ? input : "whoopsie") as AOrB; // also compiles
}

A safer yet more complicated solution is to walk the compiler through the different possibilities:一个更安全但更复杂的解决方案是让编译器通过不同的可能性:

function test(input: C): AOrB {      
  return input.kind === 'a' ? {kind: input.kind} : {kind: input.kind};
}

Either of those or something else you come up with should work.您想出的任何一个或其他东西都应该起作用。

Okay, hope that helps;好的,希望有帮助; good luck!祝你好运!

So let's get through this step by step.所以让我们一步一步来完成这个。 Let's remove A and B as interfaces and Kind as a separate type and replace all occurrences of those interfaces with their exact values:让我们将AB作为接口删除, A Kind作为单独的类型删除A并用它们的确切值替换所有出现的这些接口:

interface C {
  kind: 'a' | 'b';
}

interface D {
  kind: 'b';
}

type AOrB = { kind: 'a' } | { kind: 'b' }

function test(input: C): AOrB {
  return input;
}

function test2(input: D): AOrB {
  return input;
}

Now, in a similar manner, let's get rid of C , D and AOrB :现在,以类似的方式,让我们摆脱CDAOrB

function test(input: { kind: 'a' | 'b' }): { kind: 'a' } | { kind: 'b' } {
  return input;
}

function test2(input: { kind: 'b' }): { kind: 'a' } | { kind: 'b' } {
  return input;
}

Now, as you can see in test2 the function returns { kind: 'a' } | { kind: 'b' }现在,正如您在test2看到的,该函数返回{ kind: 'a' } | { kind: 'b' } { kind: 'a' } | { kind: 'b' } . { kind: 'a' } | { kind: 'b' } input is of type { kind: 'b' } so naturally it matches that definition. input{ kind: 'b' }所以它自然匹配该定义。

In test1 the function returns { kind: 'a' } | { kind: 'b' }test1 ,函数返回{ kind: 'a' } | { kind: 'b' } { kind: 'a' } | { kind: 'b' } , but the input is of type { kind: 'a' | 'b' } { kind: 'a' } | { kind: 'b' } ,但输入的类型为{ kind: 'a' | 'b' } { kind: 'a' | 'b' } . { kind: 'a' | 'b' } Although it is tempting to say that those definitions are matching, an object which contains a single property of type 'a' or type 'b' is not assignable to either an object with a single property of type 'a' or an object with a single property of type 'b' .尽管很容易说这些定义是匹配的,但是包含类型'a'或类型'b'的单个属性的对象不能分配给具有类型'a'的单个属性的对象或具有类型'a'的对象'b'类型'b'单一属性。 Type { kind: 'a' | 'b' }类型{ kind: 'a' | 'b' } { kind: 'a' | 'b' } allows you to do something like this: { kind: 'a' | 'b' }允许你做这样的事情:

let value: { kind: 'a' | 'b' } = {
    kind: 'a'
};
value.kind = 'a';
value.kind = 'b';

Now, replace { kind: 'a' | 'b' }现在,替换{ kind: 'a' | 'b' } { kind: 'a' | 'b' } with { kind: 'a' } | { kind: 'b' } { kind: 'a' | 'b' }{ kind: 'a' } | { kind: 'b' } { kind: 'a' } | { kind: 'b' } : { kind: 'a' } | { kind: 'b' }

let value: { kind: 'a' } | { kind: 'b' } = {
    kind: 'a'
};
value.kind = 'a';
value.kind = 'b';

The value was initialised with a value of type { kind: 'a' } so the compiler assumes that that is the type of a value that this variable contains now.value是用{ kind: 'a' }类型的value初始化的,因此编译器假定这是该变量现在包含的值的类型。 In that case, assigning b to the kind property will be considered illegal.在这种情况下,将b分配给kind属性将被视为非法。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM