Using as const
on an object in Typescript can be a useful way of creating a union type.
Take the example code below:
const availableCars = {
bmw: 'BMW',
porsche: 'PORSCHE',
mercedes: 'MERCEDES',
}
const takeForTestDrive = (car: string) => {
console.log(car)
}
You can probably see the problem already. We don't really want string
as the argument for car
in the takeForTestDrive
function. It would mean we could do the following and Typescript wouldn't complain:
takeForTestDrive('FERRARI')
This is because each value in the availableCars
is a string
so any string is permittable.
We could create a manual type union:
const takeForTestDrive = (car: 'BMW' | 'PORSCHE' | 'MERCEDES') => {
console.log(car)
}
However, each time we added or removed a car, we would also have to update the availableCars
object.
Instead, we could change our object to use as const
:
const availableCars = {
bmw: 'BMW',
porsche: 'PORSCHE',
mercedes: 'MERCEDES',
} as const
This will change all properties of the object to readonly
and set the type of each item to their literal values. We could then leverage keyof
and typeof
to dynamically create a union type for our function:
const takeForTestDrive = (
car: typeof availableCars[keyof typeof availableCars]
) => {
console.log(car)
}
Now, any time you call takeForTestDrive
, you will only be able to do so using either BMW
, PORSCHE
or MERCEDES
.
Here's an alternative option which uses an enum. Doing this would change the function signature in a way where you would be permitted to using the enum when calling the function rather than being able to pass a string even if it matches the string that's set on the enum value.
enum availableCars {
bmw = 'BMW',
porsche = 'PORSCHE',
mercedes = 'MERCEDES',
}
const takeForTestDrive = (car: availableCars) => {
console.log(car)
}
takeForTestDrive(availableCars.bmw) // works
takeForTestDrive('BMW') // Argument of type '"BMW"' is not assignable to parameter of type 'availableCars'