Home > Mobile >  Are both generic type and argument necessary in this TypeScript code?
Are both generic type and argument necessary in this TypeScript code?

Time:12-21

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.

enter image description here

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 returned data.

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)
  • Related