Video details

Benny Neugebauer - Type Guards and how they can improve your code quality - TypeScript Berlin Meetup

TypeScript
09.02.2022
English

TypeScript's biggest advantage is the syntax for typed code. By following strict typing TypeScript's compiler can warn of errors before they occur at runtime. However, it can sometimes be difficult for the compiler to infer the exact type when writing general-purpose functions. In these cases, type guards can help, and we will learn how to program them.
Benny is directing the web development at South Pole, a consultancy that supports governments and organizations in reducing their environmental footprint. He is an active contributor to open source software, helped 29 million people on StackOverflow, and runs a YouTube channel about TypeScript.
Connect with Benny: https://twitter.com/bennycode
This talk has been recorded during the TypeScript Berlin Meetup #9. Join our Meetup group here: https://www.meetup.com/typescript-berlin/

Transcript

Great to see you all. I will give a talk today about improving your code using type guards and I got already like a nice introduction. My name is Benny. In the internet I go by the name of Benny code so you can find me in many places. And during the day I work for South Pole. South Pole is a very big company helping enterprises and individuals to calculate, reduce and offset their emissions through software. So we give APIs to then do reductions through, for example, carbon credits. I also have a little side hustle. So during the day I work at South Pole and during the night I'm running TypeScript. TV So that's a YouTube channel with free tutorials about TypeScript that I published there and that also brought me here today because you are also like TypeScript fans and I also like to talk about TypeScript to get insights. And today I want to share some of the tricks that I found with typeguards. And as a preparation for this talk here, I checked what is actually a type guard because it sounds so easy. Yeah, there's a type and a guard but what does it mean like by the specification? And I came across Wikipedia and Wikipedia said that a god is actually like a statement like in your program that is a boolean expression. So it's about yes or no and that can evaluate a part of your code and tell you if you should proceed within that range or not. So this is like on the outer level. So it's about like boolean expression and that then checks a branch in your code. So a branch can be like if and else if you go branch A, else branch B. And the type guards are also for example found in python. So that's what Wikipedia says. Wikipedia says, hey, this is a typewrite, you can find them in python. I was very sad about it because I like TypeScript and I would love to have had a TypeScript example. But they bring up Python here and it's interesting that we see here the branches if and else we see that there are two types, string and float. And here is a check if it is actually a string or float and then one direction takes the assumption that it is then the string. Because here's the type guard that narrows down string or float to just string. So from two options we go down to one option and this is actually like what a type guard does. It helps you to narrow your types. If you have a lot of options, what it can be, then with the type guard you can determine one of the options. And I checked in the TypeScript handbook, this is my favorite resource to educate myself about TypeScript. And they also say, hey, we have type guards and it's an expression, a runtime expression. They say that's very important, which means that the type guard produces actual code. So what you write there when you emit then the JavaScript code, that's actual code in your JavaScript and it does then check on the runtime. So when you execute not at design time when you code but on runtime and on design time, of course the TypeScript compiler can help you. But it's an actual runtime check that guarantees you then that you have the type at hand. And why are they called guards? Because they can actually guard your code from making mistakes. Because when you have a type guard then you know in that branch you know that you have a specific thing at hand. It's not a question anymore, you know it. And TypeScript comes with three type guards that you can use. So the first one is type off. Typecard type off, very easy to remember. It is for checking primitive types. Then there is the in. So just in within you can check if the property exists. And then there is instance off. With instance off you can check if something is off the class. If you are like more a visual person like me who likes to see things like as a personal figure, then type guards look like this. These are the three built in type guards and you can also define your own. So there's also the possibility to have user defined type guards and this gives it an obstacle to build your own ship. And that's also like what I want to show today. So do you want to see the type guards? Yes. Okay, then I will leave the slideshow here and jump into Vs code because I brought some code examples with me. And then we can check how it looks in Vs code and what Inteli sense will tell us about the types. So let me hop a bit. So that's a nice loading animation. We'll check if I can duplicate the screen. And there we are. Okay. Yeah, I know that live coding can go wrong so that's why I have most of the things already written here so that we can fully focus. Is it big enough for you? Can you see the text here? Yeah. Great. Because first we have to bigger yeah. Okay, let's try it with this. We have to set some terminology. So there is for example type inference. This is when TypeScript infers the type for you we see here in TeleSense TypeScript sees that this constant is assigned to value and it tells us that the type is value, which is interesting because it doesn't tell us that it is any kind of string. It knows the specific string, it knows about the literal value. Then type annotation is where you annotate the type yourself. And although we have the value type here, we tell TypeScript that this is a string. So intelligence now shows us it can be any kind of string. So here we actually widened the type, the string that was just a value can now be any string. So we widened an arrow type, we made it bigger and then type assertions which I find very funky and dangerous because here you assert a type to something that you think might be right. So here you say value is string as string but you can also like invent something I can say that this year is now let's say prisma and TypeScript will believe that although we see that it's value but I can say it's Prisma. And then TypeScript says yeah, it's Prisma. And then we can say if our type expression equals value because we see that it's value, right? And we will see a compiler arrow because typecode says hey, this can never be true because it's Prisma, right? But it's actually value. So that's like very interesting to see that an if condition that is actually correct is here incorrect. In that case, what have we learned? We have learned what is type widening? When we turn a literal into like a wider type we will also hear about type narrowing because that's what you have type guards for and we saw that you can create fake types by using the S syntax. Let's see the first type card which is here the type of type of is the easiest one. You've seen the results like it was the one on the left and with typeof you can say okay, you have a union type here, this is a union of two and you want to check okay, is it now a string or is it a number? And you can do that with a simple type of. You have an if condition that goes back to our definition of a guard. A guard was like a boolean expression so we have a boolean expression, is that true? Yeah and if the type of input is number then that's true and then IntelliSense can tell us that here the input is a number. So we guard the type within that scope and this is the bare essence of a type guard. Our code is now guarded in that scope and we can operate here with a number and we don't have to pass it. For example, like if I would put here pass int and I put in the input then it's something that is unnecessary because it is already a number. Whereas here where we have the string, like in the else case, the assumption now is that input is a string because it's not a number and it can be only string or number. So when it's not a number, it must be a string. This is what TypeScript also knows by using that type of type guard. The next thing is the sample with the in. I have to check that sample because this sample is more interesting as it goes beyond the limitations of type of because type of has one floor or not floor, it's just not implemented like this. But type of can only check the primitives, so it can only check numbers, strings, booleans, but it has a hard time with objects. Here, for example, I check the type of dog or person if it is for example an object and both are objects. So if I check the type signature of the dog, then I see that it's an object. I see the curly braces and the same for the person, they are both objects. So this thing here doesn't help me because type of will tell me it's object for both of the cases. So when I look at my dog or person I will see that it is dog or person. Still a union TypeScript can't help me here and I can't put anything else here because if I check what Intelli sense gives me, I see only these basic things. And if I would write down something like dog it would tell me that it is not possible because that condition can only have the easy ones. So that's where in comes into play. So here I have a dog, the dog has a name, I have a person. The person also has a name. So they have the same signature so far. But they have two different methods. The dock can bark and the dock can run and the person can shout and walk. And I have a make noise function, so I want them to go very wild. And with the integrity guard I can check for a property that distinguished those types. So I can say okay, if there is a bark method that only the dog has, the TypeScript knows that it must be a dog. So the dog or person now narrows down to just the dog. So when I check now Intelli Sense I can see that I can bark or run, which I can't do as a person. And in the Elk's case I will also see then that it must be now than a person. Because if it is not a dog, then it must be a person and the person can shout or walk. This is also what you gained instead that's in for objects very useful. If we turn those objects, the types into classes, then we can make use of something else because we now have proper classes and we can then use instance off. Instance off is the same thing like in just four classes and you can check them against the constructor. Is now a question that I often hear which is okay, you have this in, you have this type of instance of why can't you just not just check with an if? Because we have the dock here and why can't I just go and say okay, if dog or person and then I check on the property that the dog own has for example bark. Why can't I just check for this and do it with a simple if? Because TypeScript will tell me her bark doesn't exist because it checks what those two have in common. They only have the name in common. And since doggo person only have the name in common that's the only thing I can check. And I can't check for bark. I actually can check for BARC which is a very cool thing which is called the never type. The never type will allow me that. So for example, I can check for a nonexistent thing of something. So let me just run an easier example. I have a user payload that has a status and I have a location payload that also has a status. Both have status number but they have two different properties which distinguish them users and locations. Location payload has locations, users payload doesn't have that. So if I check here my payload and I want to check okay for users because then I know if the payload has a user's property, it must be a user payload. Then TypeScript will tell me again, okay, that doesn't exist on the location so you can't use that here. But I can say that my location never has users and when I do that then TypeScript can help me here and say okay, that's fine. Now it knows a little more but you will see that it still isn't sure if it is user or location. It still thinks it's a union type because I need to now implement that for all other properties. I need to duplicate my code in a way for the signatures that I have always liked the same properties but was never assigned to the other one. And that's like a very tedious word. There is though a very useful case for never which is here a switch case which I find very cool. This is why I want to share it. It's a little unrelated to type guts but it's super cool to know. So let's say you have an enum and you want to handle almost all cases of that enum because your enum could live in a different file. You use a library, it's living in a different file. Now your co worker puts in a new value here for the enum and you want to know about it and you want that you handle that in your cases and you have here a switch and you handle the case online and you handle the case offline. And then you have a default case which is actually not used, right. It's not useful because there is only online and offline. So why do you have a default case? Well, if you check, what is the response now here in the default case, what can it be? TypeScript will tell you that it's never. Yeah, because it can never occur and you can make use of that because you can say well if that's the case, then I will assign like a variable here to respond status, status of type never. And whenever I now add a value here in my email. Let's say either then this thing here will run crazy because it is not never in that default case. It can now be idle and you get a compilation error and that's very cool because this makes sure that when you modify your enums and your add values. You have them to also build these cases in for the rest of your code base. So that's a very cool thing that I can advise can help to improve the code base. And what is also now nice, what you can build as well is a type predicate. So that's the fourth section, like the force type guard that I was showing the ship and this is about writing your own typecards. So we've seen the three typecards. The three heroes there and now we can build a fourth one where we can do everything we like and this one I see very often in catch clauses because there is one thing that is not so good about the TypeScript implementation here is when you're catching an error. You would like to say okay. That error is an error. But this is not possible in TypeScript. It will tell you that an error can only be any or unknown and we all know that any is something you should be very careful with. So I would then suggest to always say okay, unknown, I don't know what error that is because remember, in JavaScript you can throw new errors, you can throw a new error and then you can do this but you can also in JavaScript throw anything else, just strings, which is crazy but doable. And that's why TypeScript tells you that error here can be everything, right? It doesn't necessarily have to be an error class, an error instance class. So that's where type predicates come in handy. Actually the type predicate is let me just make it a little bigger. It's just this snippet here. This thing where you can say okay. If this condition again boolean expression. If this condition is true. Then I know that the error that I give in my candidate. My input is of the type that I'm specifying here in the predicate and here you can write all the statements you like and actually the Axios request library provides you an axios error object that has a property called Isaxis error. So you can check is that error first of all defined. And if the error is of type object, then we narrowed it down, right? We know that first of all the error is defined. We know that the error is an object. So now we can use our in check because in check is there to check properties for specific objects. So now we're checking if this object error has the properties access error when yes, we're return through otherwise false and then we can use this type guide in here and it will allow us that TypeScript will infer this or not infer anymore like not inference. Yeah, it's narrowing down. We are narrowing down our unknown object to an access or arrow object and then we can access all the properties it has. For example, we see it has a response and then on the response we get the status and then we can use that and we are safe here in that branch. For the type predicates you can go very crazy. So that's what I want to show you. In the tool belt there is a library from Naman that I want to highlight here. He created this library type predicates and this one gives you already type predicates that he implemented. For example, to check if something is an array or a string. Of course for an array you could also say hey, I want to have array this array. And then you pass in an array to use the native functionality. But this will only tell you that the argument is of any array. You don't know the value in these areas anymore then. But using like for example this library and the is array of functionality from here you can apply generics because typegots also work well with generics and you can say okay, this array needs to be validated for string values. And then you know inside here that your array is of type string. It's a string array. It's more advanced than just saying array is array. So that's what I wanted to highlight and I also reached the 20 minutes so time to say thank you to some of my resources that I've been using. Just want to show them here. So of course, Wikipedia, then I was reading about widening types, narrowing types, the never trick I found on the way. And of course for providing this cool library. Who wants to catch this? Yeah, should I perfect say any yeah, you're referring to my type predicate, right? Where I was checking the access error because I said that the input is like unknown and then I was finding out here should that go back? Big pressure works pretty well. That also works. I think you can write type predicates where you accept anything because it would be nice to throw your type predicate. Especially like if you build a library as we've seen, then it's great if that library accepts any input. But you need to be very careful because you need to check your type predicate and with different types. I had it as well with the access error type predicate when I was developing it, it worked quite well with objects. As long as I was passing object, it was cool. But when I passed in a number I saw that my type predicate crashed. And this is something that I want you also to be aware of that the type predicate that you write also will compile two JavaScript code. So it's code that is being executed and it can crash if you have a mistake in there. So when you accept anything there and you don't for example, check if it simply exists or if it is an object, then the predicate can crash. That's why I just specified the type in my predicate as well. So that inside there like I know already that for example, it is an object. So long story short, use any but really like test and your type predicate with various types. You can also catch me in a break, right?