我想根据提供的键名构建一个带有两个键的对象。
function dynamicKeys<KeyOne extends PropertyKey, KeyTwo extends PropertyKey>(
keyOne?: KeyOne,
keyTwo?: KeyTwo
) {
const normalizeKeyOne = keyOne ?? ("foo" as const);
const normalizeKeyTwo = keyTwo ?? ("bar" as const);
return {
[normalizeKeyOne]: {},
[normalizeKeyTwo]: [],
} as const;
}
问题是上面的代码返回了一个通用类型:
{
readonly [x: string]: {};
}
有没有办法让它返回以下类型:
const v = dynamicKeys(); // return type should be { foo, bar }
const v2 = dynamicKeys('baz', 'boo'); // return type should be { baz, boo }
答案 0 :(得分:3)
这里发生了一些事情,但您面临的主要问题是 TypeScript 很少为具有计算键的对象提供强类型。在您的情况下,键是通用类型,因此您将行为列为 microsoft/TypeScript#21030 中的设计限制:键类型一直扩展到 string
。许多其他 GitHub 问题都涉及到这一点,特别是 microsoft/TypeScript#13948,这仍然是一个悬而未决的问题。目前尚不清楚在不久的将来是否会在此处实现任何内容,因此我们必须通过找出所需的类型并asserting 确定该值属于该类型来解决此问题。
dynamicKeys()
的一种可能实现如下所示:
function dynamicKeys<K1 extends PropertyKey = "foo", K2 extends PropertyKey = "bar">(
keyOne?: K1,
keyTwo?: K2
) {
const normalizeKeyOne = keyOne ?? ("foo" as K1);
const normalizeKeyTwo = keyTwo ?? ("bar" as K2);
return {
[normalizeKeyOne]: {},
[normalizeKeyTwo]: [],
} as { [P in K1 | K2]: P extends K1 ? {} : [] };
}
让我们验证它是否适用于正常用例:
console.log(dynamicKeys().foo); // {}
console.log(dynamicKeys().bar.length); // 0
console.log(dynamicKeys("baz").baz); // {}
console.log(dynamicKeys().bar.length); // 0
console.log(dynamicKeys("baz", "qux").baz); // {}
console.log(dynamicKeys("baz", "qux").qux.length); // 0
看起来不错。
在那个实现中,我使用了一些并不总是正确的技巧。
第一个是我给 default specifications for the generic parameters 以便如果编译器无法推断它们,它会为 "foo"
和 {{1} 回退到 "bar"
和 K1
} 分别。当有人在没有一个或多个参数的情况下调用 K2
时,就会发生这种失败的推理。这不太正确的原因是有人总是可以在调用 dynamicKeys()
时手动指定通用参数,并且编译器不会抱怨:
dynamicKeys()
第二个是返回类型,// don't do this
try {
dynamicKeys<"boo", "hiss">().hiss.length;
} catch (e) {
console.log(e); // dynamicKeys().hiss is undefined
}
是一个 mapped type,它具有联合 { [P in K1 | K2]: P extends K1 ? {} : [] }
中每个元素的键。对于 K1 | K2
中的任意键,值类型为 K1
,对于 {}
中的任意键,值类型为 K2
。这适用于 []
和 K1
都是单个 literal type 的常见情况。这不太正确的原因是有人可以传入参数,其中 K2
或 K1
本身就是 union types,然后编译器会错误地认为输出的键比它多实际上是:
K2
这些问题中的每一个都可以自己解决或避免,代价是让事情变得更加复杂。您可以按照 this question 的答案中的方法之一处理“可能不好的默认”行为。您可以按照 this question 的答案中的一种方法来处理“键的联合”行为。这取决于你想在哪里停下来。