typescript - 打字稿动态键返回值
问题描述
我想根据提供的键名构建一个带有两个键的对象。
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 }
解决方案
这里发生了一些事情,但您面临的主要问题是 TypeScript 很少为具有计算键的对象提供强类型。在您的情况下,密钥是泛型类型,因此您会在microsoft/TypeScript#21030中将行为列为设计限制:密钥类型一直扩大到string
. 许多其他 GitHub 问题都涉及到这一点,特别是microsoft/TypeScript#13948,这仍然是一个悬而未决的问题。目前还不清楚在不久的将来是否会在这里实现任何东西,因此我们必须通过找出所需的类型并断言该值是该类型来解决它。
一种可能的实现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
看起来不错。
在那个实现中,我使用了一些并不总是正确的技巧。
第一个是我为泛型参数提供默认规范,以便如果编译器无法推断它们,它会分别回退到"foo"
和"bar"
forK1
和K2
。当有人在dynamicKeys()
没有一个或多个参数的情况下调用时,就会发生这种失败的推理。这不太正确的原因是有人总是可以在调用时手动指定泛型参数dynamicKeys()
,编译器不会抱怨:
// don't do this
try {
dynamicKeys<"boo", "hiss">().hiss.length;
} catch (e) {
console.log(e); // dynamicKeys().hiss is undefined
}
第二个是返回类型,{ [P in K1 | K2]: P extends K1 ? {} : [] }
是一个映射类型,它对 union 中的每个元素都有键K1 | K2
。对于任何 key in K1
,值类型是{}
,对于任何 key in K2
,值类型是[]
。这对于常见的情况很好,其中K1
和K2
都是一些单一的文字类型。这不太正确的原因是有人可以传入参数 whereK1
或者K2
是union types,然后编译器会错误地认为输出具有比实际更多的键:
// don't do this
const oops = dynamicKeys(
Math.random() < 0.5 ? "one" : "two",
Math.random() < 0.5 ? "three" : "four"
);
/* const oops: {
one: {};
two: {};
three: [];
four: [];
} */
try {
console.log(oops.three.length + oops.four.length);
} catch (e) {
console.log(e); // oops.three is undefined (or maybe oops.four is undefined)
}
这些问题中的每一个都可以自己解决或避免,但代价是使事情变得更加复杂。您可以通过遵循此问题答案中的一种方法来处理“可能是错误的默认值”行为。您可以通过遵循此问题的答案中的一种方法来处理“键的联合”行为。这取决于你想停在哪里。
推荐阅读
- javascript - 类型错误:songs.map 不是函数
- css - clip-path: ellipse 属性如何工作?
- javascript - jQuery preventDefault onclick 函数
- spring - Kubernetes nodeport 外部 ip 不可访问
- c++ - 使用 HTTP2 发出 TLSv1.2 请求
- python - Pandas - 检查行是否包含特定字符串并在新列中返回结果
- javascript - 如何将对象设置为常量并在反应中显示值?
- ibm-cloud - 从 IBM Cloud Function 访问 Cloudant 时出现 401 错误
- ssl - wget ERROR: The certificate of ‘xyz’ is not trusted, has expired
- c# - Outlook VSTO 加载项在 Outlook 中不可见