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?
Assigning Generic ParametersBy passing in the type with the <number> code, you are explicitly letting TypeScript know that you want the generic type parameter T of the identity function to be of type number . This will enforce the number type as the argument and the return value.
In Typescript, Type aliases give a type a new name. They are similar to interfaces in that they can be used to name primitives and any other kinds that you'd have to define by hand otherwise. Aliasing doesn't truly create a new type; instead, it gives that type a new name.
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.
UPDATE FOR TS4.7+
Hello again! TypeScript 4.7 will introduce instantiation expressions as implemented in microsoft/TypeScript#47607, which will allow you to directly specify the type parameters for a generic function without actually calling the function, so there's no longer a need to abuse classes the way shown in the rest of the answer. Here it is:
type GFooT<T,> = typeof foo<T> // (arg: T) => T
type GFooParametersT<T,> = Parameters<typeof foo<T>> // [arg: T]
type GFooReturnT<T,> = ReturnType<typeof foo<T>> // T
That's almost the same as the syntax in the question except that trailing commas are not supported after type arguments (but they are supported after type parameter declarations). Hooray!
Playground link to code
PREVIOUS ANSWER FOR TS4.6-
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.
Playground link to code
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With