Change language

Fundamental Type Guards – TypeScript Narrowing #2

Fundamental Type Guards - TypeScript Narrowing #2

Hey! Welcome to the second video in our TypeScript narrowing series, where we go from fundamentals to advanced use cases.

In this video, I wanna show you the fundamental type guards in practice.

To do that, we will build a function that formats error messages before showing them to the end user.

Our function should be able to receive multiple different types and return a formatted error message.

Are you ready? Well start by supporting only two types: string and Error To implement that function, we need to narrow the string | Error type to just a string and deal with it, then we narrow to just an Error and deal with it.

The first type guard that well explore is the typeof operator.

This operator allows us to check if a given value is a: "string", "number", "bigint", "boolean", "symbol", "undefined", "object" or a "function" So, lets use it in our function.

Whats happening here is that the typeof value === string statement is acting as a type guard for string.

TypeScript knows that the only way for the code inside that if statement to run is if value is a string so it narrows the type down to string inside the if block.

Since were returning something, value cant be a string after that if statement, so the only type left is Error.

Sometimes were not so lucky.

For example, lets add a new type to our function, a custom interface called Warning.

Now our code is broken.

Before, our value variable could only be an Error instance after the if statement.

But now, it can be Error or a Warning and the .message property doesnt exist in a Warning.

The typeof operator wont help us here because typeof value would be "object" for both cases.

One of the idiomatic ways of handling that situation in JavaScript would be to check if value has the .text property. If it does, its a Warning.

We can do that with the in operator guard.

This operator returns true if the given object has the given property.

In this case, if value has the .text property.

TypeScript knows that our if statement will only be true if value is a Warning because thats the only possible type for value that has a property called .text, so it narrows the type down to Warning inside the if block.

After the first if statement, value can be Warning | Error.

After the second if statement, it can only be Error.

Its also very common to support optional arguments, that means, allowing value to be null or undefined.

We could handle the undefined case with the typeof operator but that wouldnt work with null.

By the way, if you wanna know why it wouldnt work for null and the differences between null and undefined, I have a very short and informative video explaining just that.

Ill leave a link for it in the description.

What we could do that would work for null and undefined is to use equality operators, such as === Our if statement will only be true if value equals null or undefined, so TypeScript narrows our type to null | undefined.

Thats called equality narrowing and it also works with other comparison operators, such as: Not equals, loose equals and loose not equals But heres the thing, equality narrowing is not the idiomatic JavaScript way of checking for null | undefined.

The idiomatic way of doing this is to check if the value is truthy.

I have a one minute long video explaining what is truthy and falsy in JavaScript.

Ill put the link on the screen and in the description.

It would be nice if you could go watch that real quick so that we have the definition of truthy and falsy fresh in our minds.

Go ahead, Im waiting.

Ok.

Now that we all have the definition of truthy and falsy fresh in our minds, let me introduce you to truthiness narrowing.

Instead of using equality narrowing to check if value equals null or undefined, we can just see if its falsy.

We can do that by prefixing it with a logical NOT.

That will convert the value to a boolean and invert it.

If its falsy, itll be converted to false and then inverted to true.

So far, weve been avoiding a guard to check if value is an instance of the Error class.

I told you how were managing to do that.

We are treating all the possible types so that theres only the Error type left in the end.

That technique is very common in JavaScript and its also a form of narrowing.

The correct term for what weve been doing is "Control Flow Analysis".

Control flow analysis is the analysis of our code based on its reachability.

TypeScript knows that we cant reach the first if statement if value is not falsy.

We cant reach the second if value is not a string.

We cant reach the third if its not a Warning.

So in the end, theres only one type left, it can only be an Error.

Those types are being narrowed because TypeScript is using control flow analysis.

But we dont need to rely on control flow analysis to narrow our type to Error.

We can do it with a very simple and idiomatic JavaScript operator.

The instanceof operator.

Here we are checking if value is an instance of the Error class, so TypeScript narrows our type down to Error.

There is no type left after that last if statement, we will never reach any code that comes after it.

If youre wondering what TypeScript considers to be the type of value after all of our if statements, the answer is never.

never is a special type that represents something that is impossible, something that should never happen.

Those were the fundamental type guards, they are super useful but they will only take you so far.

In the next videos, Ill show you how to create custom type guards.

Subscribe if you dont wanna miss it.

References are in the description.

And if your company is looking for remote web developers, you can contact me and my team on lucaspaganini.com.

As always, Have a great day, and Ill see you soon!