TypeScript how to create a generic type alias for a generic function?
Given a typed generic function, I want to create a generic alias for that function, but it seems I can't. In other words, this doesn't work:
// foo.tsx
function foo<T>(arg: T): T {
return arg
}
type FooT = typeof foo // works, but alias isn't generic: <T>(arg: T) => T
type FooParametersT = Parameters<typeof foo> // sure: [unknown]
type FooReturnT = ReturnType<typeof foo> // no problem: unknown
type GFooT<T,> = typeof foo<T,> // yikes
type GFooParametersT<T,> = Parameters<typeof foo<T,>> // nope
type GFooReturnT<T,> = ReturnType<typeof foo<T,>> // fails
What I'm really trying to do is grab the type of a function in someone else's library and then build an interface around it. For example:
import { useState } from "react"
type UseStateFnT<T,> = typeof useState<T>
const wrapUseState: (toWrap: UseStateFnT<T,>) => UseStateFnT<T,> = …
Is this possible without recreating the complex typed function signature myself?
There are two different flavors of generics in TypeScript: generic functions, and generic types... and it looks like you want the compiler to transform one into the other for you, which isn't directly supported.
To be clear:
Generic types have type parameters that need to be specified before you can use them as a specific type. For example:
type GenType<T> = (x: T) => T[];
declare const oops: GenType; // error
declare const genT: GenType<string>; // okay
const strArr = genT("hello"); // string[];
const numArr = genT(123); // error!
Here, GenType
is a generic type. You need to specify the type parameter to use it as the type of a value, and then the resulting type is no longer generic. The genT
function takes a string
and returns a string[]
. It cannot be used as a function that takes a number
and returns a number[]
.
Generic functions, on the other hand, have a specific type that can act like any possible substitution of its type parameters. The value of a generic function type is still generic when you use it. The type parameter is attached to the call signature:
type GenFunc = <T>(x: T) => T[];
declare const genF: GenFunc;
const strArr = genF("hello"); // strArr: string[];
const numArr = genF(123); // numArr: number[];
Here, GenFunc
is a specific type referring to a generic function. The genF
function is still generic when it is called.
Generic functions (including generic constructor functions) can be thought of as generic values, as opposed to generic types.
These two flavors of generics are related to each other but the TypeScript type system isn't expressive enough to talk about how they are related. In some other language, you might be able to define one in terms of the other like
type GenFunc = forall T, GenType<T>; // not TS, error
or
type GenType<T> = instantiate GenFunc with T; // not TS, error
but in TypeScript you can't. Maybe if we ever get higher kinded types as requested in microsoft/TypeScript#1213... but not now. So it is not directly possible to turn GenFunc
into GenType
programmatically in the type system.
There are evil awful ways to coerce the compiler to calculate GenType
in terms of GenFunc
. The way I know of makes use of generic class property initialization and some higher order type inference for generic functions introduced in TypeScript 3.4. I'm making the compiler think it's calculating values when I don't actually have any, and then getting the type of one of these pretend values:
class GenTypeMaker<T> {
getGenType!: <A extends any[], R>(cb: (...a: A) => R) => () => (...a: A) => R;
genType = this.getGenType(null! as GenFunc)<T>()
}
type GenType2<T> = GenTypeMaker<T>['genType']
// type GenType2<T> = (x: T) => T[]
You can verify that GenType2<T>
is the same type as GenType<T>
, and if you change GenFunc
into any generic function with one type parameter, GenType2<T>
will change accordingly.
But I don't know I'd want to recommend anyone actually use this method. And it doesn't really scale or compose; if you have an object full of generic functions in one type parameter and you want to transform it to an object full of specific functions with a specified type parameter, there's no way to use this method to get it from the type system without doing it once for each object property.
Anyway, hope that helps; good luck!
Playground link to code