不幸的是,您遇到了两个类型脚本限制或错误...第一个是次要的,但它会导致主要的.
小问题是microsoft/TypeScript#13948,其中一个computed property key或union type的对象被赋予了index signature,这不必要地将对象类型扩展到了不太有用的类型.例如:
const v = { [Math.random() < 0.5 ? "a" : "b"]: 123 };
// const v: { [x: string]: number;}
运行时,值v
将为{a: 123}
或{b: 123}
.因此,如果脚本的类型是{a: number} | {b: number}
,那么它应该是v
.相反,它推断出{ [x: string]: number }
...就编译器而言,它完全不知道v
的密钥名是什么;只是它的所有属性都是number
型.
就你而言,你有
const obj = { [day]: true };
// const obj: { [x: number]: boolean; }
obj
已经从{0: boolean} | {1: boolean} | {2: boolean}
扩大到{[x: number]: boolean}
,完全忘记了Day
.
这个问题并不是灾难性的,因为实际的类型至少是compatible,而所需的类型是次优的.
第二个bug是microsoft/TypeScript#27144,其中带有索引签名的对象类型可分配给带有所有optional properties、even if the property value types are incompatible的类型.这很糟糕:
const v: { [k: string]: number; } = {a: 1};
const x: { a?: string } = v; // no error!
v
的类型有一个索引签名,其中所有属性都必须有number
类型值.同时,如果x
类型存在,则它具有string
类型值的单个可选属性.这些不应该被认为是兼容的,但它们是兼容的.因此,在没有编译器警告的情况下,可以错误地将v
分配给x
.
就你而言,你有:
const p: TTime = o; // no error
其中,TTime
类型具有所有可选属性,并且被认为与o
的索引签名类型兼容,尽管boolean
和string
不兼容.
Yuck.
所以这两个问题的相互作用导致了你的问题.在这里,"正确"的做法可能是等待/游说microsoft/TypeScript#27144得到修复.如果你想讨论这个问题????, 不会痛的.这可能也帮不了什么忙.谁知道什么时候,甚至是否会得到解决.
与此同时,你必须处理好它.
一种方法是为microsoft/TypeScript#13948做一个变通方案.我们不必满足于索引签名类型,而是可以编写一个帮助函数,直接确定该值是更有用的类型.这样地:
const kv = <K extends PropertyKey, V>(
k: K, v: V
) => ({ [k]: v }) as { [P in K]: { [Q in P]: V } }[K];
kv
函数接受一个键和一个值,并生成一个对象,该对象具有与该键和值对应的一个属性:
const y = kv("a", 123);
// const y: { a: number; }
如果在键为联合类型时执行此操作,则会得到联合类型的输出:
const w = kv(Math.random() < 0.5 ? "a" : "b", 123);
// const w: {a: number} | {b: number}
所以你可以用它来制作obj
:
const obj = kv(day, true);
// const obj: { 0: boolean; } | { 1: boolean; } | { 2: boolean; }
既然obj
没有索引签名,我们就不会遇到microsoft/TypeScript#27144:
const obj: TTime = kv(day, true); // error,
// boolean is not assignable to string
编译器能够看到{0: boolean} | {1: boolean} | {2: boolean}
与TTime
不兼容,因为boolean
与string
不兼容.
Playground link to code