简体   繁体   中英

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. I then have a function accepting registry key as string and this function should then return a value of the associated generic type. 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; to 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]> . 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 . 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:

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.


So that compiles as desired, but unfortunately it re-defines registry in terms of Registry , which you're not allowed to do. Instead, your requirement is that registry is given to you and cannot be modifierd. Luckily we can just define the RegistryReturn mapping type in terms of it:

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:

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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