Typescript Generics for those that have been avoiding it
Typescript has been very fun to learn, but there is one concept that I found very daunting:
I have been avoiding it in the beginning, but decided to tackle it and document my understandings here.
Basics in 5 minutes
Let’s start with a simple Javascript function. We will use this example to play around with generics.
function makeInfo () {
let info
function getInfo () {
return info
}
function setInfo (val) {
info = val
}
return { getInfo, setInfo }
}
makeInfo()
returns an object with two functions getInfo()
and setInfo()
. Pretty simple, right?
const { getInfo, setInfo } = makeInfo
setInfo('git')
console.log(getInfo()) // git
setInfo('foo')
console.log(getInfo()) // foo
Let’s start enforcing types with generics.
Restrict type to a string
Let’s restrict the type of info
to a string.
function makeInfo () {
let info: string
function getInfo () {
return info
}
function setInfo (val: string) { info = val
}
return { getInfo, setInfo }
}
Now the type
of info is restricted to string
.
const { getInfo, setInfo } = makeInfo
setInfo('foo') // <--- works
setInfo(1) // <--- compilation error
Restrict info type to string or number
How do we restrict the info type to be of string, or of number?
Option 1
The following might come as a natural guess.
function makeInfo () {
let info: string | number
function getInfo () {
return info
}
function setInfo (val: string | number) { info = val
}
return { getInfo, setInfo }
}
But it IS NOT PRACTICAL.
Why isn’t it practical? Because it allows both a number or a string, whereas we want two different states of info: one for a string and one for a number.
What the hell am I talking about? Maybe the following code block helps. The stuff below is sort of what we want.
const stringOnlyInfo = makeInfo()
const numberOnlyInfo = makeInfo()
stringOnlyInfo.setInfo('foo')
stringOnlyInfo.setInfo(1) // <--- want this to be disallowed
numberOnlyInfo.setInfo(1)
numberOnlyInfo.setInfo('foo') // <--- want this to be disallowed
How can we do that?
Option 2
Use generics!!
function makeInfo<T> () { let info: T
function getInfo () {
return info
}
function setInfo (val: T) { info = val
}
return { getInfo, setInfo }
}
<T>
is just a type you pass into a function.
const numInfo = makeInfo<number>() // info can only be a number
numInfo.set('foo') // <--- will throw compilation error
const stringInfo = makeInfo<string>() // set info to be a string only
stringInfo.set(1) // <--- will throw compilation error
I chose T
for Type, but you can choose anything you want.
Common names:
- E for Element
- S for State
- T for Type
- K for Key
- V for Value
Problem: <T>
could also be a boolean, or any other type
Nothing is stopping us from using other types.
makeInfo<boolean>() // <--- will still work
makeInfo<Array>() // <--- will still work
But we want to restrict makeInfo()
to just string
and number
.
How do we do that?
Use extends
function makeInfo<T extends number | string>()
Now, makeInfo()
will only accept number
or string
.
makeInfo<number>() // ok
makeInfo<string>() // ok
makeInfo<boolean>() // <--- will throw compilation error
Tip: Default type
You can specify a default type the same way you do for function arguments.
function makeInfo<T extends number | string
= string>
makeInfo() // <--- of type string
makeInfo<number>() // <--- need to explicitly declare number type for number info
That’s it for the basics! Hopefully this helps you better read Typescript code.
At first, the plethora of all the <T>
, <S>
, <K, V>
might look intimidating, but at the end of the day they are just simple type definitions.
Happy programming!