我正在寻找一种类型安全的方法来执行以下类型的数据转换:
// original data type
const nameToDaysAsWWEChamp = {
"Stone Cold Steve Austin": 529,
"The Rock": 378,
"Hulk Hogan": 2188,
"Ric Flair": 118,
} as const;
// target data type
type TargetType =
| { name: "Stone Cold Steve Austin"; daysAsWWEChamp: 529 }
| { name: "The Rock"; daysAsWWEChamp: 378 }
| { name: "Hulk Hogan"; daysAsWWEChamp: 2188 }
| { name: "Ric Flair"; daysAsWWEChamp: 118 };
// attempted transformation
function f(name: keyof typeof nameToDaysAsWWEChamp): TargetType {
return { name, daysAsWWEChamp: nameToDaysAsWWEChamp[name] }; // error!
}
函数f
想要返回以下类型的对象:
{
name: "Stone Cold Steve Austin" | "The Rock" | "Hulk Hogan" | "Ric Flair";
daysAsWWEChamp: 529 | 378 | 2188 | 118;
}
我如何才能说服typescript“尊重Map”并将f
的返回类型缩小到TargetType
?
2条答案
按热度按时间q8l4jmvw1#
TypeScript无法抽象
f
的主体,以理解将name
缩小到联合类型keyof typeof nameToDaysAsWWEChamp
的特定成员的每一种可能性,在输出为{name, daysAsWWEChamp: nameToDaysAsWWEChamp[name]}
时,都会导致有效的TargetType
。它根本不会尝试这样分析;相反,它只是将name
的类型作为联合,因此daysAsWWEChamp
也是一个联合,并导致你提到的太宽的类型。它不明白name
的联合类型与daysAsWWEChamp
的联合类型是相关的。这是microsoft/TypeScript#30581的主题;TypeScript并不真正支持相关的联合类型。在microsoft/TypeScript#47109中描述了这种情况下的建议方法...它涉及使用generics而不是联合。你必须重构你的类型,以便将操作视为通用的indexed accesses转换为mapped types。你可以阅读问题以了解对此的详细描述。对于你的示例,它看起来像这样:
这里
TargetType
现在是一个泛型 * 分布式对象类型 *。如果你指定泛型类型参数作为NameToDaysAsWWEChamp
的某个特定键,你会得到原始TargetType
联合的对应成员。如果你指定一个 union,你也会得到对应的联合。如果你根本不指定它,你会得到完整keyof NameToDaysAsWWEChamp
的默认值,所以它和你以前的TargetType
是一样的,这意味着在某种意义上,TargetType
和以前是一样的:现在,我们可以将
f
写成一个 generic 函数,它接受K
类型的name
参数,并返回TargetType<K>
类型的输出:这是可行的,因为编译器将输出视为
{ name: K, daysAsWWEChamp: NameToSaysAsWWEChamp[K] }
,这与定义TargetType<K>
相同(至少对于K
是单个文字类型;对于联合体来说,这有点复杂,编译器做了一些技术上不安全的事情,参见microsoft/TypeScript#48730,但这是故意的,这里的行为是你想要的)。作为额外的奖励,
f()
函数的输出将比TargetType
更具体、更窄,如果需要,可以使用这些更窄的类型:编译器知道
hh
是一个特定的TargetType
成员,而mineral
是一对特定成员中的一个。Playground代码链接
x7yiwoj42#
这个有用吗?
//尝试转换