This is hard to explain without source code, so take a look at this snippet:
type Type1 = { num1: number; str1: string };
type Type2 = { num2: number; str2: string };
type Type3 = { num3: number; str3: string };
type Type4 = { num4: number; str4: string };
enum MyEnum {
ONE,
TWO,
THREE,
FOUR,
}
type MyTypes<T> = T extends MyEnum.ONE
? Type1
: T extends MyEnum.TWO
? Type2
: T extends MyEnum.THREE
? Type3
: T extends MyEnum.FOUR
? Type4
: never;
const urls = {
[MyEnum.ONE]: 'http://one.com',
[MyEnum.TWO]: 'http://two.com',
[MyEnum.THREE]: 'http://three.com',
[MyEnum.FOUR]: 'http://four.com',
};
const fetchData = (_url: string): unknown => ({
// ...
});
export const myFun = <T extends MyEnum>(type: T) => {
const data = fetchData(urls[type]);
return { data: data as MyTypes<T> };
};
const one = myFun<MyEnum.ONE>(MyEnum.ONE);
const two = myFun<MyEnum.TWO>(MyEnum.TWO);
const three = myFun<MyEnum.THREE>(MyEnum.THREE);
const four = myFun<MyEnum.FOUR>(MyEnum.FOUR);
myFun()
here receives the same twice:
- As generic type, used statically to type the
data
returned. - As an argument, to be used in runtime to choose the right
url
Note this works like a charm. Even writing something like myFun<MyEnum.ONE>(MyEnum.TWO)
is not allowed as I don't allow passing a different types for generic and argument. Returned type is correctly typed and myFun()
works as I need.
What is the problem? I find it ugly and I would like to use myFun()
passing the type only once, in one of the following ways:
const a = myFun<MyEnum.ONE>(); // Option 1
const b = myFun(MyEnum.ONE); // Option 2
Is it possible?
I tried:
- Option 1 (using a generic only), but in runtime it is not available to select the proper
url
. - Option 2 (using only the argument) as
const myFun = (type: MyEnum) => {...}
, but then I cannot assign the proper type to returneddata
.
Note: Any other suggestion to simplify things here is welcome. For instance, MyTypes<T>
definition looks ugly for me but I don't know any other way to do it.
CodePudding user response:
Thanks to @Tobias S. I've discovered that TypeScript does not need the generic to be provided when the function is called, as it infers it from the argument. So it is ok to define myFun()
as follows:
export const myFun = <T extends MyEnum>(type: T) => {
const data = // ...
return { data: data as MyTypes<T> };
};
But you can use it as
const { data } = myFun(MyEnum.ONE);
And TypeScript will infer data
type from the generic, infered from the value received as argument (as it is the same provided by the generic). It rocks!
argument ------> generic --------> data
(provided) (inferred) (inferred)