Is there functionally any difference between the two generic type parameters below?.
function funcA<T>() { }
function funcB<T extends {}>() {}
I have seen them both used and am confused as to the differences?
This article opts to use the term type variables, coinciding with the official Typescript documentation. T stands for Type, and is commonly used as the first type variable name when defining generics. But in reality T can be replaced with any valid name.
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.
We can add generic type parameters to class methods, static methods, and interfaces. Generic classes can be extended to create subclasses of them, which are also generic.
You can get the Type that represents T , and use the IsInterface property: Type type = typeof(T); if (type. IsInterface) { ... } If you want to know which interface is passed, just use == to compare the Type objects, e.g.
Note: I'll assume you're using a version of TypeScript 3.5 or later; in TypeScript 3.5 a change was made so that generic type parameters are implicitly constrained by unknown
instead of the empty object type {}
, and some minor details about the difference between funcA()
and funcB()
changed. I don't want to make a long post even longer by talking about how things used to be in TS3.4 and below.
If you don't explicitly constrain a generic type parameter via extends XXX
, then it will implicitly be constrained by unknown
, the "top type" to which all types are assignable. So in practice that means the T
in funcA<T>()
could be absolutely any type you want.
On the other hand, the empty object type {}
, is a type to which nearly all types are assignable, except for null
and undefined
, when you have enabled the --strictNullChecks
compiler option (which you should). Even primitive types like string
and number
are assignable to {}
.
So compare:
function funcA<T>() { }
funcA<undefined>(); // okay
funcA<null>(); // okay
funcA<string>(); // okay
funcA<{ a: string }>(); // okay
with
function funcB<T extends {}>() { }
funcB<undefined>(); // error
funcB<null>(); // error
funcB<string>(); // okay
funcB<{ a: string }>(); // okay
The only difference is that T extends {}
forbids null
and undefined
.
It might be a little confusing that {}
, a so-called "object" type, would accept primitives like string
and number
. It helps to think of such curly-brace-surrounded types like {}
and {a: string}
as well as all interface
types not necessarily as "true" object types, but as types of values where you can index into them as if they were objects without getting runtime errors. Primitives except for null
and undefined
are "object-like" in that you can treat them as if they were wrapped with their object equivalents:
const s: string = "";
s.toUpperCase(); // okay
And therefore even primitives like string
are assignable to curly-brace-surrounded types as long as the members of those types match:
const x: { length: number } = s; // okay
If you really need to express a type that only accepts "true", i.e., non-primitive objects, you can use the object
:
const y: object & { length: number } = s; // error
const z: object & { length: number } = { length: 10 }; // okay
But I (seriously) digress.
Okay, hope that helps; good luck!
Playground link to code
Yes. In funcB
, T must extend {}
which means pretty much anything except null
and undefined
. T can be a primitive though.
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