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()) // fooLet’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 disallowedHow 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 errorconst stringInfo = makeInfo<string>() // set info to be a string only
stringInfo.set(1) // <--- will throw compilation errorI 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 workBut 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 errorTip: 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 infoThat’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!