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

时间:2019-05-16 18:36:11

标签: typescript tuples

这两个示例的行为应相同,但第二个错误。为什么?

// 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"'.
}

您可以try it on the playground

1 个答案:

答案 0 :(得分:3)

更新:2019年5月30日,TypeScript 3.5的发行版引入了smarter union type checking,它针对对象类型(如{a: "x"} | {a: "y"}进行了修复,但似乎对元组类型没有任何作用(如{{ 1}})。不确定这是否是故意的。


在“示例1”中,将["x"] | ["y"]初始化为a的事实产生了很大的不同。控制流分析将"x"的类型缩小为a,尽管您的注释为"x"

"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. 将匹配[a],因为编译器知道["x"] | ["y"]的类型为[a]


因此,示例1仅同时成功。通常,这将失败。编译器通常不会将["x"]视为等同于[A] | [B]。前者被视为比后者严格狭窄的类型。

[A | B]

这可能令人惊讶,因为实际上type Extends<T, U extends T> = true; type OkayTup = Extends<[string | number], [string] | [number]>; type NotOkayTup = Extends<[string] | [number], [string | number]>; // error! 类型的每个值都可以分配给[A | B]类型。当您看到类似的属性袋版本时,也会发生同样的惊奇:

[A] | [B]

同样,type OkayObj = Extends<{a: string | number}, {a: string} | {a: number}>; type NotOkayObj = Extends<{a: string} | {a: number}, {a: string | number}>; // error! 被认为是严格比{a: A} | {a: B}狭窄的类型,尽管事实是您很难被迫提出无法分配的后一种类型的值到前者。

那么,这是怎么回事?好吧,这似乎是TypeScript的intentionaldesign limitationWord of Language Architect说:

  

对于您的示例来说,如果没有错误地进行类型检查,我们将必须考虑{a: A | B}形式的类型等同于{ x: "foo" | "bar" }。但是这种等价仅适用于具有单个属性的类型,在一般情况下并非如此。例如,认为{ x: "foo" } | { x: "bar" }等同于{ x: "foo" | "bar", y: string | number }是不正确的,因为第一种形式允许所有四种组合,而第二种形式仅允许两种特定的组合。

(注意:等价关系在比上述情况略多的情况下适用...仅在每个联合组成部分中的不同属性具有单个联合中所有可能值的情况下适用属性案例,因此{ x: "foo", y: string } | { x: "bar", y: number }等效于{x: string | number, y: boolean, z: string}

我会说这是一个设计局限性...检测相对较少的情况下,资产合并可以折叠/扩展将是非常昂贵的,而且实施起来不值得。


在实践中,如果您发现自己遇到了合并属性合并,而编译器没有进行验证,但您知道它是安全的,请展示出卓越的智慧,并assert摆脱困境:

{x: string, y: true, z: string} | {x: string, y: false, z: string} | {x: number, y: true, z: string} | {x: number, y: false, z: string}

好的,希望能有所帮助;祝你好运!