简体   繁体   English

从关联到特定对象键的 object 推断特定类型

[英]Infer specific type from an object associated to a specfic object's key

I have a registry object in a shape of {[key]: <T>()=>T} , the T s can be absolutely anything as I want to easily add more entries into it in the future.我有一个形状为{[key]: <T>()=>T}的注册表 object, T绝对可以是任何东西,因为我希望将来可以轻松地向其中添加更多条目。 I then have a function accepting registry key as string and this function should then return a value of the associated generic type.然后我有一个 function 接受注册表项作为字符串,然后这个 function 应该返回关联的泛型类型的值。 But I can't get it to work, because it returns union of generic types returned by all the functions in the registry.但我无法让它工作,因为它返回注册表中所有函数返回的泛型类型的联合。
The description is kind of unwieldy, so I hope the simple working example describes what I intend to do in enough detail.描述有点笨拙,所以我希望这个简单的工作示例能够足够详细地描述我打算做什么。

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');

I can get it work by changing return desiredValue;我可以通过更改return desiredValue; to return desiredValue as ReturnType<typeof registry[T]>;return desiredValue as ReturnType<typeof registry[T]>; , but isn't there a cleaner way? ,但是没有更清洁的方法吗?

The compiler can't really do much type analysis on conditional types that depend on generic type parameters, like ReturnType<typeof registry[T]> .编译器实际上无法对依赖泛型类型参数的条件类型进行太多类型分析,例如ReturnType<typeof registry[T]> If you want to write your function in a way that the compiler "understand"s, you should refactor to use distributive object types as described in microsoft/TypeScript#47109 .如果您想以编译器“理解”的方式编写您的 function,您应该重构为使用分布式 object 类型,如microsoft/TypeScript#47109中所述。 The idea is to start with a very basic mapping type like这个想法是从一个非常基本的映射类型开始,比如

interface RegistryReturn {
    alpha: string;
    beta: number;
}

and then represent your other operations as mapped types on that:然后将您的其他操作表示为映射类型

type Registry = { [K in keyof RegistryReturn]: () => RegistryReturn[K] }
const registry: Registry = {
    alpha: stringFn,
    beta: numberFn
}

Your generic function could then just index into that mapped type, and things work:您的通用 function 然后可以索引到该映射类型,并且一切正常:

const registryEater = <K extends keyof RegistryReturn>(
    registryKey: K
): RegistryReturn[K] => {
    const desiredFn = registry[registryKey];
    const desiredValue = desiredFn();
    return desiredValue; // okay
};

The desiredFn variable is seen to be of type Registry[K] , and then desiredValue() is seen to be of type RegistryReturn[K] , specifically because Registry is defined as a mapped type as described in microsoft/TypeScript#47109. desiredFn变量被视为Registry[K]类型,然后desiredValue()被视为RegistryReturn[K]类型,特别是因为Registry被定义为映射类型,如 microsoft/TypeScript#47109 中所述。


So that compiles as desired, but unfortunately it re-defines registry in terms of Registry , which you're not allowed to do.这样就可以根据需要进行编译,但不幸的是,它根据Registry重新定义了registry ,这是不允许您这样做的。 Instead, your requirement is that registry is given to you and cannot be modifierd.相反,您的要求是registry已提供给您并且无法修改。 Luckily we can just define the RegistryReturn mapping type in terms of it:幸运的是,我们可以根据它定义RegistryReturn映射类型:

type RegistryReturn = { [K in keyof typeof registry]: 
  ReturnType<typeof registry[K]> 
}

And then just assign registry to a new variable of type Registry so that the mapped type lookup still happens:然后只需将registry分配给Registry类型的新变量,这样映射类型查找仍然会发生:

const registryEater = <K extends keyof RegistryReturn>(
    registryKey: K
): RegistryReturn[K] => {
    const _registry: Registry = registry; // new variable
    const desiredFn = _registry[registryKey];
    const desiredValue = desiredFn();
    return desiredValue; // still okay
};

That still compiles.那仍然编译。 The only thing left to do is make sure callers get the behavior they want:剩下要做的唯一一件事就是确保调用者获得他们想要的行为:

const thisIsString: string = registryEater('alpha'); // okay
const thisIsNumber: number = registryEater('beta'); // okay

Looks good!看起来不错!

Playground link to code 游乐场代码链接

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM