简体   繁体   中英

Typescript: How can I get return type of function based on argument type?

I want return type to be based on "config" argument

now return type of exampleFn function is empty {}

interface Interface {
    a: number;
    b: string;
}
const DEFAULT_VALUES = {
    a: (num: number) => 1 + num,
    b: (str: string) => 'a' + str,
}
const exampleFn = <T extends Partial<Interface>>(config: T) => {
    const map = {};

    Object.entries(config).forEach(([key, val]) => {
        map[key] = DEFAULT_VALUES[key];
    });
    
    return map;
};

const example1 = exampleFn({ a: 123 }); // I want example1 return type to be "{a: (num: number) => number}"
const example2 = exampleFn({ b: 'asd' }); // I want example2 return type to be "{b: (str: string) => string}"
const example3 = exampleFn({ a: 123, b: 'asd' }); // I want example3 return type to be "{a: (num: number) => number, b: (str: string)} => string"

is it possible?

The compiler won't be smart enough to figure this out on its own, but you can certainly describe the type you want and use type assertions inside the implementation of exampleFn() to prevent it from complaining... keeping in mind that such type assertions shift the burden of type safety from the compiler to you.

Here's the type I think you want:

{ [K in Extract<keyof T, keyof Interface>]: typeof DEFAULT_VALUES[K] }

Basically you are making a mapped type where the keys are the keys from T which are also present in Interface (it is possible for T to contain more keys because T extends Partial<Interface> allows such extension; if you really want to prohibit this you can, but for now I'm going to leave this as you have it), and the values are the corresponding types from the DEFAULT_VALUES value.

Here's the implementation:

const exampleFn = <T extends Partial<Interface>>(config: T) => {
   const map = {} as any;

   Object.entries(config).forEach(([key, val]) => {
      map[key] = DEFAULT_VALUES[key as keyof Interface];
   });

   return map as { [K in Extract<keyof T, keyof Interface>]: typeof DEFAULT_VALUES[K] };
};

You can see that I am asserting that key is keyof Interface (since key is only known to be string by the compiler) and that map is the desired return type. Let's see how it works:

const example1 = exampleFn({ a: 123 });
console.log(example1.a(123)); // 124
console.log(example1.b); // undefined
// error!  --------> ~
// Property 'b' does not exist on type '{ a: (num: number) => number; }'
const example2 = exampleFn({ b: 'asd' });
console.log(example2.b("asd")); // aasd
const example3 = exampleFn({ a: 123, b: 'asd' });
console.log(example3.b("asd")); // aasd
console.log(example3.a(123)); // 124

Looks good to me.

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