如果计算的键不是单个literal type,则TypeScript不能精确地用computed property表示对象的类型.如果键是generic类型,如K
或K1
,则键被加宽为string
并产生字符串index signature.这种类型不是wrong,但比你想要的要宽.microsoft/TypeScript#13948更多信息
到目前为止,如果你知道一些编译器不知道的类型,最简单的方法是使用type assertion:
const output = {
[key1]: obj[key1],
[key2]: obj[key2],
assignment: ['test'],
} as KOutput<Obj, K | K1>
断言很容易,但你现在负责把类型正确.如果你的断言被证明是不正确的,那么你可能会在运行时不高兴.
如果您想要更好的东西,那么您就需要开始负责告诉编译器它可以期望的计算(computed)属性是什么样子.事实证明这有点困难.{[k]: v}
的类型是not,通常是Record<typeof k, typeof v>
.毕竟,如果k
是类型"a" | "b"
,那么输出将类似于{a: any} | {b: any}
和not {a: any; b: any}
.
这里有一个函数,我有时会使用它来获得更精确的类型,但代价是更复杂的(在实现中仍然需要使用类型断言):
function kv<K extends PropertyKey, V>(
k: K, v: V
): { [P in K]: { [Q in P]: V } }[K] {
return { [k]: v } as any
}
然后你可以重构transform()
来使用它:
const transform =
<Obj, K extends keyof Obj, K1 extends keyof Obj>(
key1: K, key2: K1, obj: Obj) => {
const output = {
...kv(key1, obj[key1]),
...kv(key2, obj[key2]),
assignment: ['test'],
};
return output
};
如果你判断一下,你会发现输出类型非常复杂.但是当你用合理的输入调用它时,你会得到你期望的输出类型:
const ret = transform('key1', 'key2', { key1: 'value1', key2: 'value2' });
// const ret: { key1: string; } & { key2: string; } & { assignment: string[]; }
console.log(ret.assignment.map(x => x.toUpperCase())) // ["TEST"]
console.log(ret.key1.toUpperCase()) // "VALUE1"
console.log(ret.key2.toUpperCase()) // "VALUE2"
{ key1: string; } & { key2: string; } & { assignment: string[]; }
型相当于KOutput<{key1: string, key2: string}, "key1" | "key2">
型. 但是如果你try 用KOutput
类型实际上是annotate the return type,你会得到一个错误:
/* Type '{ [P in K]: { [Q in P]: Obj[K]; }; }[K] &
{ [P in K1]: { [Q in P]: Obj[K1]; }; }[K1] &
{ assignment: string[]; }' is not
assignable to type 'KOutput<Obj, K | K1>' */
那是因为它是not那种类型的.只有当K
和K1
是单字符串文字类型时,它们才是等价的. 如果你把这个叫做:
const x = transform(Math.random() < 0.5 ? "a" : "b", "c", { a: 0, b: 0, c: 0 });
有KOutput
的版本会声称x
有a
,b
和c
作为键,而实际上它有a
或b
和c
,这就是上面疯狂的东西给你的:
/* const x: ({ a: number; } | { b: number; }) & { c: number; } & { assignment: string[]; } */
就我个人而言,我不知道是否值得担心这些边缘案件.但是它们是存在的,TypeScript根本不担心它们,而是 Select 索引签名.如果你想要简单的方法,请使用类型断言.如果你想走一条艰难的路,那就开始经历一个折磨人的逻辑,确定一个计算的属性对一个类型实际意味着什么.
Playground link to code