简体   繁体   English

TypeScript字符串联合不能分配给函数中的元组联合

[英]TypeScript union of strings not assignable to union of tuples in function

The behaviour of these two examples should be identical, yet the second one errors. 这两个示例的行为应相同,但第二个错误。 Why? 为什么?

// Example 1:
const a: 'x' | 'y' = 'x'; 
const b: ['x'] | ['y'] = [a]; // ok

// Example 2:
function fn(a: 'x' | 'y') {
  const b: ['x'] | ['y'] = [a];
  //    ^
  // Type '["x" | "y"]' is not assignable to type '["x"] | ["y"]'.
  //   Type '["x" | "y"]' is not assignable to type '["x"]'.
  //     Type '"x" | "y"' is not assignable to type '"x"'.
  //       Type '"y"' is not assignable to type '"x"'.
}

You can try it on the playground . 您可以在操场上尝试

UPDATE: 2019-05-30 the release of TypeScript 3.5 introduces smarter union type checking which fixes this for object types (like {a: "x"} | {a: "y"} , but doesn't seem to do anything to tuple types (like ["x"] | ["y"] ). Not sure if that's intentional or not. 更新:2019-05-30 TypeScript 3.5的发行版引入了更智能的联合类型检查 ,该检查针对对象类型(例如{a: "x"} | {a: "y"}进行了修复,但似乎并未做任何事情元组类型(例如["x"] | ["y"] )。不确定这是否是有意的。


In "Example 1", the fact that a is initialized to "x" makes a big difference. 在“示例1”中,将a初始化为"x"这一事实有很大的不同。 The control flow analysis narrows the type of a down to just "x" despite your annotation as "x" | "y" 控制流分析变窄的类型a下降到只有"x"尽管你的注释为"x" | "y" "x" | "y" : "x" | "y"

let a: "x" | "y" = "x";
console.log(a === "y"); // error!
// This condition will always return 'false' 
// since the types '"x"' and '"y"' have no overlap.

So then of course in this case [a] will match ["x"] | ["y"] 因此,在这种情况下,当然[a]将匹配["x"] | ["y"] ["x"] | ["y"] , since [a] is known by the compiler to be of type ["x"] . ["x"] | ["y"] ,因为编译器知道[a]的类型为["x"]


Therefore, Example 1 only succeeds coincidentally. 因此,示例1仅同时成功。 In general, this fails. 通常,这将失败。 The compiler does not generally see [A] | [B] 编译器通常看不到[A] | [B] [A] | [B] as equivalent to [A | B] [A] | [B]等同于[A | B] [A | B] . [A | B] The former is seen as a strictly narrower type than the latter. 前者被视为比后者严格狭窄的类型。

type Extends<T, U extends T> = true;
type OkayTup = Extends<[string | number], [string] | [number]>; 
type NotOkayTup = Extends<[string] | [number], [string | number]>; // error!

This may be surprising, since in fact every value of type [A | B] 这可能令人惊讶,因为实际上[A | B] Type [A | B] should be assignable to type [A] | [B] [A | B]应该可分配为类型[A] | [B] [A] | [B] . [A] | [B] This same surprise happens when you look at the analogous property-bag version: 当您查看类似的属性袋版本时,也会发生同样的惊奇:

type OkayObj = Extends<{a: string | number}, {a: string} | {a: number}>;
type NotOkayObj = Extends<{a: string} | {a: number}, {a: string | number}>; // error!

Again, {a: A} | {a: B} 同样, {a: A} | {a: B} {a: A} | {a: B} is seen to be a strictly narrower type than {a: A | B} {a: A} | {a: B}被视为比{a: A | B}严格更窄的类型{a: A | B} {a: A | B} , despite the fact that you'd be hard pressed to come up with a value of the latter type that wasn't assignable to the former. {a: A | B} ,尽管您很难为后一种类型分配一个值,但该值不能分配给前一种类型。

So, what's going on here? 那么,这是怎么回事? Well, it seems that this is either intentional or a design limitation of TypeScript. 好吧,这似乎是TypeScript的故意设计限制 The Word of Language Architect says: 语言设计师的话说:

For your example to type check with no errors we would have to consider types of the form { x: "foo" | "bar" } 对于您的示例,如果没有错误地键入check,我们将不得不考虑以下形式的类型{ x: "foo" | "bar" } { x: "foo" | "bar" } to be equivalent to { x: "foo" } | { x: "bar" } { x: "foo" | "bar" }等效于{ x: "foo" } | { x: "bar" } { x: "foo" } | { x: "bar" } . { x: "foo" } | { x: "bar" } But this sort of equivalence only holds for types with a single property and isn't true in the general case. 但是这种等价仅适用于具有单个属性的类型,在一般情况下并非如此。 For example, it wouldn't be correct to consider { x: "foo" | "bar", y: string | number } 例如,考虑{ x: "foo" | "bar", y: string | number } { x: "foo" | "bar", y: string | number } { x: "foo" | "bar", y: string | number } to be equivalent to { x: "foo", y: string } | { x: "bar", y: number } { x: "foo" | "bar", y: string | number }等于{ x: "foo", y: string } | { x: "bar", y: number } { x: "foo", y: string } | { x: "bar", y: number } because the first form allows all four combinations whereas the second form only allows two specific ones. { x: "foo", y: string } | { x: "bar", y: number }因为第一种形式允许所有四种组合,而第二种形式仅允许两种特定的组合。

(Note: the equivalence holds in slightly more cases than mentioned above... it only applies where the properties that are different in each union constituent take on all possible values of the union in the single-property case. So, {x: string | number, y: boolean, z: string} is equivalent to {x: string, y: true, z: string} | {x: string, y: false, z: string} | {x: number, y: true, z: string} | {x: number, y: false, z: string} ) (请注意:等价关系在比上述情况略多的情况下适用...仅在每个联合组成中不同的属性在单属性情况下具有该联合的所有可能值的情况下才适用。因此, {x: string | number, y: boolean, z: string}等效于{x: string, y: true, z: string} | {x: string, y: false, z: string} | {x: number, y: true, z: string} | {x: number, y: false, z: string}

I'd say this is a design limitation... detecting the relatively rare cases where property unions can be collapsed/expanded would be very expensive, and it's just not worth it to implement. 我想说这是一个设计局限性...检测相对较少的情况下,资产合并可以折叠/扩展将是非常昂贵的,而且实施起来不值得。


In practice, if you find yourself faced with a union property merge thingy that the compiler doesn't verify but that you know is safe, demonstrate your superior intellect and assert your way out of it: 在实践中,如果发现自己遇到了合并属性合并问题,而编译器未进行验证但您知道它是安全的,请展示出卓越的才智并断言自己的出路:

function fn(a: 'x' | 'y') {
  const b = [a] as ['x'] | ['y'] // I'm smarter than the compiler 🤓 
}

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

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

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