TS Fun
[ View accompanying code on Github ]TS Fun
While I was building out my portfolio, I discovered a funny quirk with Typescript generics .
A Quick Primer
For the uninitiated, a generic is simply a component (function, class, variable,
etc) that spans types
. You’ll commonly see them in polymorphic functions. The
contrived example we’ll be using is pulled directly from the TS docs:
function identity<Type>(arg: Type): Type {
return arg;
}
I’ve always read this from left-to-right, which lead me to believe that Type
needs to be declared if arg
is going to be casted correctly.
In my mind, there were 2 exceptions, which it turns out, are only partially true. The exceptions were:
- a constraint is declared with the
extends
keyword
function identityElement<Type extends HTMLElement>(arg: Type): Type {
return arg;
}
- a default value is declared with the
=
operator:
function identityElement<Type = HTMLDivElement>(arg: Type): Type {
return arg;
}
Enter “Type Argument Inference”
So there I was click-clacking along, when I absent-mindedly forgot to include an
argument to the generic and yet, the correct type was returned. This wild
phenomenon is known as type argument interference
. Let me explain:
What’s happening is TS is correctly inferring the type of the functional
argument, which it assigns to Type
. So in reality, the following return the
same types
var a = identity<string>("");
var b = identity("");
And More!
Even cooler is that it respects the type differences between var
, let
and
const
. As you know, the const
declarator means a variable immutable and
therefore, its type is typically its value:
/**
* This is of type `string` since it's mutable.
* TS correctly infers it's a string and therefor any mutations will not change its type.
*/
var a = "hello";
/**
* This, however, is of type `"goodbye` since it's immutable.
*/
const b = "goodbye";
This same inference is applied to generics:
/** The following are all type `"hello"` since they're immutable */
const a = identity("hello");
const hello = "hello!";
const results5 = identity(hello);
/** While these are all type `string` since they're mutable */
var goodby = "goodbye!";
const b = identity(goodby);
const goodbyeForGood = "GOODBYE!";
var c = identity(goodbyeForGood);
Application
If you want to see this in action, you can checkout the source code to the site you’re on right now: