Giving TypeScript generic functions access to type variables

57 views Asked by At

When using generics in TypeScript, it's often useful for a function of generically-typed arguments to have access to the type. There's a standard pattern to do this with named functions:

interface TestInterface<T> {
    foo: (input: T) => string;
};

const interfaces: TestInterface<string>[] = [
    {foo: (x: string) => `foo-${x}`},
    {foo: (x: string) => `bar-${x}`},
];

function func<T>(x: T, index: number): string {
    const int: TestInterface<T> = interfaces[index];
    return `ack-${int.foo(x)}`
}

However, trying to do a similar thing with anonymous functions doesn't seem to work:

const func: <T>(x: T, index: number) => string = (x, index) => {
    const int: TestInterface<T> = interfaces[index];
    return `ack-${int.foo(x)}`
}

This latter attempt fails tsc checks, saying "Cannot find name 'T'" when we try to specify the type of int.

While this is obviously a highly simplified example, there are plenty of cases where actual access to T within the function definition is needed. While the use of named functions is a simple workaround, it's stylistically odd in a codebase that strongly prefers the more modern style; is there any way to achieve this using anonymous functions? If not, should this be fixed within tsc?

2

There are 2 answers

2
Nicolas On

There is a workout to allow usage of generic type in anonymous function. While it might look unorthodox to the average TS user, it end up working quite well.

You can use the keyword unknown to define something generic. In that case, you could use T extends unknown in your anonymous function definition :

const func: <T extends unknown>(x: T, index: number) => string = (x, index) => { /* ... */ });

This will allow you to use generic type in your parameters.

It is not possible to use T directly as a type, inside the anonymous function (I'm not sure why). But since your parameters x is using the T as a type, you can use the keyword typeof to use it's type ( which end up being T ).

const func: <T extends unknown>(x: T, index: number) => string = (x, index) => {
    const int: TestInterface<typeof x> = interfaces[index];
    return `ack-${int.foo(x)}`
}

This syntax seams to be working in the typescript playground.

You would call the function like so :

func<string>('test', 1); 

I'm not sure if that answer your question, but that's the best I could find.

2
Rubydesic On

How about this:

const func = <T>(x: T, index: number): string => {
    const int: TestInterface<T> = interfaces[index];
    return `ack-${int.foo(x)}`
}