Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it possible to set the generic type of a function without calling it in Typescript?

Tags:

The question is in the title. Below an example of what I'm trying to achieve:

const apiResponse = Promise.resolve({data: 1} as {data: any}); // Assume I don't know the type of data
const mapData = <T>(res: {data: T}): T => res.data;

const x = apiResponse.then(mapData<number>); // This one fails to compile
const y = apiResponse.then(mapData); // Succeeded but y is of type any
const z = apiResponse.then(_ => mapData<number>(_)); // Succeeded in some case it is inconvenient to forward parameters
like image 675
Alexandre Annic Avatar asked Dec 11 '19 17:12

Alexandre Annic


People also ask

How do you define a generic type in TypeScript?

Generics allow creating 'type variables' which can be used to create classes, functions & type aliases that don't need to explicitly define the types that they use. Generics makes it easier to write reusable code.

How do you initialize a generic type in TypeScript?

In C# we can define a generic type argument and instantiate this type at runtime: public class Factory { public T Create<T>() where T: new() { return new T(); } } var factory = new Factory(); var person = factory.

How do generics work in TypeScript?

The TypeScript documentation explains Generics as “being able to create a component that can work over a variety of types rather than a single one.” Great! That gives us a basic idea. We are going to use Generics to create some kind of reusable component that can work for a variety of types.


1 Answers

Personally I don't see anything wrong with your "verbose" call

const z = apiResponse.then(_ => mapData<number>(_)); 

but if you want something else, read on:


Generic function types, where a single function can act as multiple types of function, only let you specify the generic type parameter when you call it. Your mapData function has the following type:

type GenFunc = <T>(res: { data: T; }) => T
const verifyMapData: GenFunc = mapData; // okay

Note that GenFunc is not a generic type alias; it is a specific type alias which refers to a generic function type. You can think of the type parameter T as "belonging to" the call signature and not to the name of the function or the name of any type alias of the function. Since it doesn't belong to the name of the function you can't write mapData<T> by itself and since it doesn't belong to the GenFunc type name you can't write GenFunc<T> either.


Compare this to the distinct but related generic type alias which refers to a family of specific function types:

type GenAlias<T> = (res: {data: T; }) => T

In this type, the generic type parameter "belongs to" the name of the type alias and not to the function type it represents.

Now it is unfortunately true that TypeScript's type system currently lacks the expressiveness necessary to take the GenFunc type and generate the GenAlias<T> type automatically. For now at least, you need to write out GenAlias<T>'s type definition yourself by hand, as I just did.


What the compiler can do is recognize that a value of type GenFunc is assignable to a variable of type GenAlias<T> for any specific T you want, so the following compiles just fine:

const mapDataNumber: GenAlias<number> = mapData; // okay

And therefore you can get the following to compile without an extra function call, if it matters:

const w = apiResponse.then(mapDataNumber); // Promise<number>

Furthermore if you don't want to waste a line on a variable assignment you can use a type assertion for a little less type safety but with absolutely no runtime effect:

const v = apiResponse.then(mapData as GenAlias<number>); // Promise<number>

Either one of those should work for you.


Okay, hope that helps. Good luck!

Link to code

like image 89
jcalz Avatar answered Oct 01 '22 23:10

jcalz