不能分配给函数中元组并集的字符串的TypeScript并集

不能分配给函数中元组并集的字符串的TypeScript并集,typescript,tuples,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"]

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

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

您可以。

更新:2019-05-30 TypeScript 3.5的发行版引入了针对对象类型(如
{a:“x”}{a:“y”}
,但似乎对元组类型没有任何作用(如
[“x”].[“y”]
)。不确定这是有意还是无意的


在“示例1”中,
a
被初始化为
“x”
这一事实起到了很大的作用。控制流分析将
a
的类型缩小到仅
“x”
,尽管注释为
“x”|“y”

因此,在这种情况下,当然
[a]
将匹配
[“x”]|[“y”]
,因为编译器已知
[a]
类型为
[“x”]


因此,示例1只是巧合地成功了。一般来说,这是失败的。编译器通常不会将
[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:a}{a:B}
被认为是比
{a:a | B}
更窄的类型,尽管您很难找到后一种类型的值,而这种值不能分配给前者

那么,这里发生了什么?好吧,看起来这是一个或一个类型脚本。上面说:

对于您的示例,键入没有错误的检查,我们必须考虑窗体<代码> {x:“fo”“bar”}<代码>等价于<代码> {x:“fo”}{x:“bar”}。但是,这种等价性只适用于具有单个属性的类型,在一般情况下是不正确的。例如,考虑<代码> {x:“FO”“bar”,y:字符串编号} /代码>等价于<代码> {x:“fo”,y:string }{x:“bar”,y:数字}是不正确的。因为第一种形式允许所有四种组合,而第二种形式只允许两种特定组合

(注意:等价性适用于比上述情况稍多的情况……它仅适用于每个并集组成部分中不同的属性在单个属性情况下具有并集的所有可能值的情况。因此,
{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}

我认为这是一个设计上的限制……检测相对罕见的情况下,财产联盟可能会崩溃/扩张,这将是非常昂贵的,而且不值得实施


在实践中,如果您发现自己面临编译器没有验证但您知道是安全的union property merge问题,请展示您的卓越智慧和解决方法:

函数fn(a:'x'|'y'){
常量b=[a]作为['x'].['y']//我比编译器聪明,我认为
'x'.'y'
会更自然地映射到
['x'.'y'].
,这确实有效:
常量b:['x'.'y']=[a]
type Extends<T, U extends T> = true;
type OkayTup = Extends<[string | number], [string] | [number]>; 
type NotOkayTup = Extends<[string] | [number], [string | number]>; // error!
type OkayObj = Extends<{a: string | number}, {a: string} | {a: number}>;
type NotOkayObj = Extends<{a: string} | {a: number}, {a: string | number}>; // error!