我正在重新设计以前使用香草Javascript实现的实现。我遇到了一个有趣的情况,我确实有解决方案,但是我认为对于我来说,我有一个出色的解决方案,似乎没有用。
基本上,我有一个带有两个嵌套对象的对象,它们的索引键入为它们的键的字符串文字,其中两个对象都具有一些其他对象键,但没有完全重叠。然后,我有一个可以接收任何一个键的函数,并且可以接收另一个键来访问该对象中的值之一。我为键创建了自定义类型保护,并创建了第二组类型保护以确认传递的第二个键是对象之一的键。然后,我创建了一个使用两个键的函数,并且应该返回一个具有正确匹配的键的对象。但是,打字稿似乎在函数中并未确信我正在使用验证器函数,因此从函数返回的键只能是肯定可以访问上层对象键的键。
那是一个非常不清楚的解释,我认为一些示例代码会使它变得更好,所以这里是:
const decoder = {
foo: {
foo: "bar",
bar: "foo"
},
bar: {
foo: "bar",
},
};
type FooKeys = keyof typeof decoder["foo"];
type BarKeys = keyof typeof decoder["bar"];
const isFooKey = (key: string): key is FooKeys => Object.prototype.hasOwnProperty.call(decoder["foo"], key);
const isBarKey = (key: string): key is BarKeys => Object.prototype.hasOwnProperty.call(decoder["bar"], key);
const isFoo = (key: string): key is "foo" => key === "foo";
const isBar = (key: string): key is "bar" => key === "bar";
const validator = (key: string, secondKey: string) => {
if (isFoo(key) && isFooKey(secondKey)) {
return { key, secondKey }
}
if (isBar(key) && isBarKey(secondKey)) {
return { key, secondKey }
}
return false;
}
// Here comes where the issue arises
const someFunc = (key: string, nestedKey: string) => {
const validated = validator(key, nestedKey);
if (validated) {
return decoder[validated.key][validated.secondKey];
}
return null;
}
任何人都可以向我解释为什么这不符合我的预期,这是打字稿的缺点还是我的推理或实现的问题?如果有人对我的问题有更好的解决方案,我很想听听!
答案 0 :(得分:1)
这里的根本问题是validated
是我一直所说的correlated record type,而TypeScript对此并没有很好的支持。问题是decoder[validated.key]
和validated.secondKey
的类型都是联合类型;前者的类型为{ foo: string; bar: string; } | { foo: string; }
,后者的类型为"foo" | "bar"
。 TypeScript的类型系统几乎无法表示它们之间存在 correlation 的事实。
通常,如果我有两个联合类型值,每个联合类型都有两个成员,例如declare const x: A | B; declare const y: C | D;
,则 pair [x, y]
的类型类似于[A, C] | [A, D] | [B, C] | [B, D]
。但是您碰巧知道,例如,如果x
的类型为A
,那么y
的类型将为C
,反之亦然……因为x
与y
的类型相关。因此[A, D]
和[B, C]
是不可能的。因此,[x, y]
应该只属于[A, C] | [B, D]
。但是编译器无法自行推断,因此抱怨这些不可能的情况。
在您的情况下,编译器无法验证decoder[validated.key][validated.secondKey]
将是有效的索引操作。它认为decoder[validated.key]
可能是{ foo: string }
,而validated.secondKey
可能是"bar"
。所以它抱怨。
您可以采取一些措施来解决此问题。一种只是使用type assertion来告诉编译器不要担心。这是最不安全的类型,但不会更改您的运行时代码:
(decoder[validated.key] as { foo: string, bar: string })[validated.key]
您基本上已经宣称decoder[validated.key]
是其两种可能类型的交集,因此您可以安全地为其"foo" | "bar"
属性建立索引。
您可以编写冗余代码来引导编译器通过两种可能性:
validated.key == "foo" ?
decoder[validated.key][validated.secondKey] :
decoder[validated.key][validated.secondKey]
此处使用control flow analysis将validated
缩小为两种可能性,此后validated.key
和validated.secondKey
不再是并集类型。
通常,这是处理相关记录的两种主要方法。对于您的代码,第三种可能性提示自己:由于您的validator()
函数实际上分别经历了两种可能性,因此您可以将索引移入该函数以利用控制流分析:
const validatorAndDecoder = (key: string, secondKey: string) => {
if (isFoo(key) && isFooKey(secondKey)) {
return { key, secondKey, val: decoder[key][secondKey] }
}
if (isBar(key) && isBarKey(secondKey)) {
return { key, secondKey, val: decoder[key][secondKey] }
}
return null;
}
const someFunc2 = (key: string, nestedKey: string) => {
const validated = validatorAndDecoder(key, nestedKey);
if (validated) {
return validated.val
}
return null;
}
好的,希望能有所帮助;祝你好运!
答案 1 :(得分:0)
您可以这样做:
function pluck<T, K extends keyof T>(o: T, propertyName: K): T[K] {
return o[propertyName];
}
function validator(obj: typeof decoder, key: string, secondKey: string) {
if (isFoo(key) && isFooKey(secondKey)) {
const item = pluck(obj, key);
const subItem = pluck(item, secondKey);
return subItem;
}
if (isBar(key) && isBarKey(secondKey)) {
const item = pluck(obj, key);
const subItem = pluck(item, secondKey);
return subItem;
}
return null;
}
索引类型
使用索引类型,可以使编译器检查使用动态属性名称的代码。
请参见here(pluck
)