In this post (the last of the series 😢) we are going to understand type assertions and compare them against type declarations.
What do you use type assertions for?
There are situations where you know more than TypeScript can infer.
let item: Item = {...}
type Item = {
name: string
}
type FileItem = Item & {
extension: string
}
// We know for sure that item
// is also a file
printFile(item as File)
Golden rule for using assertions
You can only assert from one type to another if either type is a subset of the other. 🧐
type Car = {
numOfDoors: number
}
type Airplane = {
numOfEngines: number
}
const car: Car = {numOfDoors: 5}
// Conversion of type 'Car' to type 'Airplane' may be a mistake
// because neither type sufficiently overlaps with the other.
const airplane = car as Airplane
An exception to this rule is when using unknown
or any
.
You can use these to bypass it:
-
unknown
because is the universal set -
any
because disables the type checking
const airplane = car as unknown as Airplane
Prefer type declarations to type assertions
This is a mistake that I've seen a lot!
type Car = {
numOfDoors: number
numOfAirbags: number
}
// Error: Property 'numOfAirbags' is missing
const car: Car = {numOfDoors: 5}
// No error
const car = {numOfDoors: 5} as Car
When you use type assertions you are telling TypeScript to get out of the way, with type declarations you are making your intentions clear so it can help you.
Is as const
a type assertion?
It is not.
Despite having a similar syntax, as const
is used to hint the type system about values being immutable.
It is very situational, but could be useful for using the values of an array as literals, for example:
const coolBands = ['Oasis', 'AC/DC', 'Foo Fighters'] as const
// type CoolBands = "Oasis" | "AC/DC" | "Foo Fighters"
type CoolBands = typeof coolBands[number]
Or for using the values of an object:
const coolBandsAndSingers = {
'Oasis': 'Liam Gallagher',
'AC/DC': 'Brian Johnson',
'Foo Fighters': 'Dave Grohl'
} as const
// type CoolBands = "Oasis" | "AC/DC" | "Foo Fighters"
type CoolBands = keyof typeof coolBandsAndSingers
// type CoolSingers = "Liam Gallagher" | "Brian Johnson" | "Dave Grohl"
type CoolSingers = typeof coolBandsAndSingers[CoolBands]
As it is the last post of this series, I also want to go through some topics that couldn't get a post for their own.
Don't type everything!
I did it, and probably so did you.
It is not bad, but can make the code too verbose and therefore harder to read.
As a rule of thumb, you should type very well:
- Function and method signatures (parameters and return types)
- Variables and constants when using object literals, to take advantage of excess property checking.
In a TDD like spirit, you should know your input and output types before implementing a function/method, so typing it from the beginning makes it easier for you to implement it.
Typing return types usually avoids implementation errors, specially if your function has many "paths".
Don’t use uppercase variants of primitive types
Probably you noticed that String
or Number
exist and wonder if you should use them as types.
The answer is no. Just stick to lowercase types for primitives string
, number
, boolean
, etc.
These uppercase variants exist primarily for convenience, for example:
// charAt is not a property of
// the string primitive
"hey".charAt(1)
JavaScript wraps the string
primitive in String
under the hood and uses the charAt
method of String
and then throws that object away.
// These wrappers don't have behave
// as primitives
new String('hey') === new String('hey')
'hey' === new String('hey')
It's been a pleasure to write this series and I wish you a very productive experience with TypeScript 🙂
Thanks for reading!
Top comments (0)