我有一个{[key]: <T>()=>T}
形状的注册表对象,T
可以是任何东西,因为我想在将来轻松地添加更多的条目。然后我有一个函数接受注册表键作为字符串,这个函数应该返回一个关联的泛型类型的值。但是我不能让它工作,因为它返回注册表中所有函数返回的泛型类型的并集。
这个描述有点笨拙,所以我希望这个简单的工作示例足够详细地描述我打算做的事情。
type valFn<T> = () => T
declare const stringFn: valFn<string>;
declare const numberFn: valFn<number>;
const registry = {
alpha: stringFn,
beta: numberFn,
// ... i want to easily add some records in the future without modifying +registryEater+ every time i do so, all the records will be of type +key: ValFn<Something>+
};
// CHANGES CAN BE MADE ONLY DOWN FROM HERE
type ReturnType<T> = T extends valFn<infer R> ? R : never
const registryEater = <T extends keyof typeof registry>(registryKey: T): ReturnType<typeof registry[T]> => {
const desiredFn = registry[registryKey];
const desiredValue = desiredFn();
// TS2322 because +desiredValue+ is string | number, but i need it do be a specific type depending on real value of +registryKey+
return desiredValue;
};
// CHANGES CAN BE MADE ONLY UP FROM HERE
const thisIsString = registryEater('alpha');
const thisIsNumber = registryEater('beta');
我可以通过将return desiredValue;
更改为return desiredValue as ReturnType<typeof registry[T]>;
来使其工作,但是没有更干净的方法吗?
1条答案
按热度按时间2skhul331#
编译器不能对依赖于generic类型参数的conditional types(如
ReturnType<typeof registry[T]>
)进行类型分析。如果你想以编译器“理解“的方式编写函数,你应该重构为使用microsoft/TypeScript#47109中描述的 * 分布式对象类型 *。其思想是从一个非常基本的Map类型开始,如然后将其他操作表示为mapped types:
然后,您的泛型函数可以只Map类型index into,这样就可以正常工作了:
desiredFn
变量被视为Registry[K]
类型,然后desiredValue()
被视为RegistryReturn[K]
类型,具体原因是Registry
被定义为Map类型,如microsoft/TypeScript#47109中所述。这样就可以编译了,但是不幸的是它用
Registry
重新定义了registry
,这是不允许的。相反,你的要求是registry
是给你的,不能被修改。幸运的是我们可以用它来定义RegistryReturn
Map类型:然后将
registry
赋给一个Registry
类型的新变量,这样Map的类型查找仍然会发生:这样仍然可以编译。剩下要做的唯一事情是确保调用者获得他们想要的行为:
看起来不错!
Playground代码链接