Type Assertions - the as
keyword
Otherwise known as "trust me bro"
When using TypeScript it can be tempting to use Type Assertions as a way of saying "trust me TypeScript, I know the type, hush now.".
However, this can be risky. You have now broken the safety contract that TypeScript normally provides.
For example:
const element = document.getElementById("this-is-definitely-an-anchor");
if (element) {
// Type Assertion - trust me, it's an anchor
console.log((element as HTMLAnchorElement).href.includes("https://"));
}
In the above example the Type Assertion as HTMLAnchorElement
is telling TypeScript "trust me, I know this is definitely an anchor element. So don't bother warning me that href might not be defined".
What's the problem?
It's clearly an anchor, it's not hurting anyone, why are we getting all worked up?
The true beauty of TypeScript, similar to unit tests, is it protects your future self, not just your current self. If you later refactor some code, you want TypeScript to complain at compile time, instead of failing at run time.
Your colleague, you know the one, always changing stuff, let's call him Jeff. Jeff updates the html from an anchor to a button. Your artisan code above will now fail at run time (in the browser)!
Uncaught TypeError: Cannot read properties of undefined (reading 'includes')
It fails because we assured TypeScript, through our assertion, that the element was an anchor. Maybe it was our confidence, maybe our bravado, but TypeScript believed us without question. Don't get me wrong, Jeff is also to blame for changing the HTML.
What is a better way to handle unknowns?
Instead of saying "trust me", you should prove to TypeScript that the unknown is a particular type by "narrowing it".
instanceof
is one way to do this:
const element = document.getElementById("this-is-definitely-an-anchor");
if (element && element instanceof HTMLAnchorElement) {
console.log(element.href.includes("https://"));
}
Sometimes you may not have a constructor to reference. You can also narrow types with the in
operator:
const payload = JSON.parse(request.body);
if ('message' in payload) {
console.log(payload.message.toUpperCase());
}
To be super clear, both instanceof
and in
are JavaScript runtime operators. This is very intentional as you are proving to TypeScript that you will be handling it at runtime.
Type predicates are another useful technique. I will do a separate write up on those.