Video Summary and Transcription
Today's Talk discusses TypeScript and its relationship with JavaScript, emphasizing the importance of studying JavaScript first. The Talk explores type guards, runtime type checking, and the use of the 'never' type to handle errors and ensure type safety. It also delves into alternative solutions for type handling, branded types, and schema-based validation using Zod. The Talk concludes with advice on migrating to TypeScript and the need for beginner-friendly documentation and strictness in code.
1. Introduction to TypeScript
Today, I will talk about TypeScript and JavaScript. TypeScript loves JavaScript, but the reverse is not true. You should study JavaScript before moving to TypeScript. JavaScript code is the only code that runs in production. Now, let's jump into the code and look at an example of working with TypeScript.
So, today I try to talk something strange in TypeScript. First of all, let me introduce myself. I'm Luca De Pupol. I'm Italian. I'm a software developer at Niaform, and, if you don't know us, we're an Irish consultant company, but we are based in the US, central Europe, Brazil, and some positions also in India. I love also TypeScript and JavaScript.
I try to run my YouTube channel but in the last time, I don't have too much time for it, and also try to write a post for tech people. I love running, hiking, and animals, especially dogs.
By the way, I want to start with a question, and don't be shy. Who loves TypeScript? Raise your hands, but if you don't like it, you can leave your hands down without any problem. Not all the people! And who loves JavaScript? I hope all, because you are here! But not all! Why? Why are you here? Who loves Java? Why?
Okay. I want to start with small tips. TypeScript loves JavaScript. So if you love JavaScript, you should also love TypeScript. But JavaScript doesn't love TypeScript. If you have a JavaScript code, it can also be a TypeScript code, but if you have a TypeScript code, it's not a JavaScript code. Please remember, before you have to study JavaScript, and then you can move to TypeScript. Always. Otherwise, TypeScript is just magic for you, and at run time, it's a mess. And remember, JavaScript code is the only code that runs in production. So, if you don't know it, your customer called your boss, probably! Okay, and that's it for my tips, and I jumped to the code.
I think this talk is like a sort of example, and stuff that we can do with TypeScript. So let me try to write code with this desk, otherwise, I will show the problem, and I will show the solution directly from the solution folder. So I suppose that if you are working with TypeScript, you start to create the types for your object. For instance, you can have, in this case, the person object, the company object, and in some part, you have a sort of union type, for instance, the user type. That is a union between the person and the company. You start to create your object, the person and the company, you can create a new person from the current person, but in some part of your code, you want to print the user. Okay? But the user can be a person or a company. But you want to be strict, and maybe you want to create a function for the person, and create a specific message for the person, and then create a specific message for the company. And this function accepts a person or a company.
2. Type Guards in TypeScript
To detect if the user is a person or a company and call the right function, TypeScript has a feature called type guard. Type guard functions are simple functions that return a Boolean indicating whether a condition is met or not. By using type guards, you can explicitly define the type of the user object. However, the text contains some confusing parts.
What happened here is you want to detect if the user is a person or a company and call the right function. And have also the benefit from TypeScript, for instance, as you can notice here, if I check if the person is a person in this line, basically, TypeScript doesn't know that this user is a person. But I want to be express with my code.
How can I do this? TypeScript has a cool feature called type guard. What type guard is a way to create function that allows TypeScript to detect what you want to do. For instance, in this case, I want to say, okay, I want to create a function that accepts the user and returns something that says, okay, the user is a person or not. What type functions are basically a simple function like this? That should return a Boolean. If it's true, you meet the condition, otherwise not.
How this type guard works, basically, you can do this, use person. User is a person. You are saying to TypeScript, in this way, okay, if the result of this function is true, user is a person. Otherwise is another kind of user. And we have also to implement it. How? Basically, it's a simple function that accepts the user, and you can do, for instance, this for the solution. Fiscal code is part of the user.
Now there is another... What is the problem? Sorry. Person. User. Fiscal code in user. Why? Fuck, God! Sorry. I can move to the solution. It's more easy. Also because right here it's sort of a mess. Sorry. Okay. Basically, I don't know what is it. This is another... Let me go back. Sorry.
3. Checking User Types at Runtime
To check if a person is a person and a company is a company at runtime, add a literal type to the user type. This allows you to detect the type of the user object. By using literal types, you can handle different types of users and ensure that TypeScript can detect them correctly. However, if the print function is used in production without TypeScript, it's important to throw an error at runtime if the user doesn't meet the expected condition. Additionally, using the never type can provide build-time notifications if new user types are added in the future.
User. What error? Ah! It's right! Ah! So, fuck! No, this is right. Sorry. But... Let me go here. Fantastic. This works. I don't know why. What is the difference? Ah, sorry. No. This is the solution, and it's perfect.
Image, you can... Let me go back and give you some examples. What you can do to check at runtime if the person is a person and the company is a company is add a literal type to our type. What happened here is basically you can have the type that at runtime allows you to detect if the person is a person and the company is a company. Basically, what happened here is that you can use the user and check if the user has the type person, you are sure that this person is a person. If the user has the type company, you can detect if the user is a company. What happened here, as you can notice, user now in this branch is a real person, as you can notice, and also TypeScript can detect it.
For instance, as you can notice, I use the return here, and TypeScript is so smart that in this line, basically, what happens is that the user is a company, because TypeScript knows that here you can have user that can be a person or a company. At the end, here, for TypeScript, user can be only a simple company, if you want. But you can implement this company if you want, and in this branch, it's the same. The user is a company. Then what happens is it's fantastic at TypeScript time, okay? Because you handle all the possible types. But if you expose this print function in production, someone that basically don't use TypeScript can call the print, and maybe the user is not formatted in the best way, like express in your function. What we can do is throw the error, as we already know, basically, we can throw something here, and at runtime, we will receive an error if the user doesn't meet the condition. But I want more. I want to receive a notification at build time if, for instance, someone in the future decides to add a new user type, for instance, an animal, okay? How I can do this using the never type. As you can notice here, the user is of never type. Because for TypeScript, now, if you look in the type, you already handle the person and the user. So, basically, it's impossible to arrive in this part of the code.
4. Handling User Types with the Never Type
To prevent errors, assign the user to the never type if it's not handled. Adding a new type will result in a compilation error before production. Another way to handle types is by using a switch statement. By checking the literal type, TypeScript can determine which type is being handled.
What happened here, basically, you can assign the user to the never type, and this is possible just because the user now is of never type. For instance, if you forgot to handle the company, like in this way, as you can notice, the should never happen has an error. Because user here can be of type company, and you haven't handled the problem.
Image, you haven't handled the type. For instance, if you in the future decide to add the animal type, what will happen, you will receive a compilation error before the code is in production. If you forget to handle the function. And this is fantastic. Because you can prevent the error before release the code in production. And this is why I love TypeScript.
We can do this also in another way. Imagine you want to use the switch. I know switch is not the best structure in JavaScript, but you can also do type check with a switch. Now, imagine you want to create a calculator that basically accepts a user. You can check the type here. In the case, you have the benefit of the literal type. So basically here, TypeScript knows that you want to handle all the person or a company. I already handle the company in the line after. So TypeScript knows that you want to handle probably the person. And basically, what happens here, here the user is a user. But in this branch, the user is a person. And the same is here.
5. Handling Errors and Type Checking in TypeScript
To handle errors and ensure type safety, use the never type for default cases and always return a type in functions. TypeScript can detect if all code branches are handled at build time, preventing runtime errors. You can also use type checking in filters, although it may require using the beta version of TypeScript. The upcoming release will address this limitation, making type checking more convenient for developers.
And the same is here. Then you can also handle the default error, the default part using the never if you want. But what I suggest is always to add the default with the never type and raise the error. That basically is the best solution because at runtime, you can also handle if someone called the function without the right shape. And the second one is always return the type. Because in this case, if I forgot to handle, for instance, the company, I receive an error at runtime. At the build time. Why? Because my function has a lock. TypeScript is also able to understand if you already handle all the possible branches of your code. In this case, I only handle the person, for instance. And the result is this. I receive a notification during the build and not in production when the user runs my JavaScript code. And this is fantastic.
Now, you can do, for instance, the same also if you want to handle the filter. Okay? Imagine you have a list of user where you have person, company, or new person, and you want to filter the list just for the person. How? Basically, using the filter, you can call directly your isPerson function. Come on. This isPerson function, and you can see here the person is a list of person and not a list of user. Then there is a small problem. So, if I want to call the user.type equal to person, as you can notice, it doesn't work. Why? Because in this TypeScript version, TypeScript is not able to understand that this is a type function. But we are lucky. And if you move, if we move to the new version of TypeScript, so, the beta version, as you can notice now, here, the person is a list of person. And you can do the same also with using the type of equal number, TypeScript, string, etc, etc. Using for instance a list of number or string. And this is fantastic because many times one of the problems with the old version of TypeScript is okay, I'm sure that this is a type check for my user. In this case, for my type. And the people have to create a specific type that just to pass the function through the filter, for instance. But, fortunately, with the next release, TypeScript will fix the problem. And every developer should be happy.
6. Alternative Type Handling Solution
To handle the type more effectively, you can use a sub function to continue the code flow only if the type meets the condition. Otherwise, an error is raised. This approach allows for more precise control over the type handling. Implementing the sub function requires using a function and passing the condition to check the type. If the condition is not met, an error is raised; otherwise, the code continues. This method ensures that the user type matches the expected type, such as company.
Okay. But we have also another possible solution to handle the type. And if you want to be, for instance, more rude, you don't want to return a Boolean, but you want to continue in the flow of your code only if the type or whatever you are checking meets the condition. Otherwise, there is an error. We can do this using a sub function. So, basically, as you can notice here, I created a user. And this is a simple user. I used it just to force the type as user.
And here the user is a user. And if you go there, unfortunately, the user continues to be a user. But I want that the user in this line matches with the company type. Because I'm sure that if I pass through this function, the user is a company.
Now I can do this. Basically, let me jump in the solution and avoid the live coding. So, what happened? We can implement the function in this way. So, basically, the function is a function. You have to use a function. You cannot use a variable that is a function, unfortunately. But the syntax is this. You say, okay. I want to host it. And then you have to pass the condition. For instance, in this case, I want to check if the user is a company. Then the business logic to check the type is your business. TypeScript cannot know it. And basically in this case, if the user type is different from company, okay. I raise an error. Otherwise, I continue to the rest of the code. What happened here, as you can notice, here the user is a user.
7. Handling Types and Branded Types
If at runtime you call the asset function with a different type that is not a company type, an error is raised. Being more expressive with types, you can create specific types with rules that match your business domain. Branded types in TypeScript allow you to create types with specific fields that describe the branded type and enforce the rules in your code.
What happened here, as you can notice, here the user is a user. But if I'm here, the user is a company. And basically what happened is, okay, if at runtime you call the asset function with a different type that is not a company type, you raise an error. And please catch the error and send it to Sentry or wherever you want and look on Slack when we have a problem because you have to fix it.
Okay. Perfect. And this is the first part. Then, when we have this in mind, we want to be more expressive with our type. Let me jump here and show another example. Okay. I want to create the password type, the integer type and the fiscal code type. Then maybe I want to create the personality type and then I want also to create my person type. Fantastic. But basically, in my domain, password is not just a simple string. It's a string with a specific rule. For instance, a minimum length of 5 and a maximum of 18 and some strange rule. An integer is a number, yep, but it's a number without decimal. A fiscal code is another string with a specific rule.
How we can show, how we can be more expressive with our type and maybe don't have this situation. For instance, if I create a password here, password is a simple string, okay? And I don't want that the password, the simple string match with the password type. I want that someone check the password and convert my string in a real password type that describes my business domain, okay? How can I do this? Using the branded type.
Basically, if I jump here, what are branded types? Basically, branded types are magic, okay? Inside of TypeScript. This is one of the most magic part of TypeScript. Basically, you can create a brand const that basically is something that allows the TypeScript compiler to detect you have a specific field in your types that describe the branded type. Then you can create your branded type that accepts your type. You can have a string, you can have a complex object or whatever you want. And you have to give a specific name to this branded type. What does it mean? You can describe your password in this way. Basically, as you can notice now, password is not just a simple string. It is a string with this strange syntax that describes that this string is part of the brand password type.
8. Using Schema-Based Validation with Zod
The password, integer, and fiscal code do not match their respective types. To simplify the code, you can use a schema-based validation library like Zod. This library allows you to define JavaScript code that can work at runtime and also be converted into TypeScript types. By using schemas, you can enforce specific rules for each type, such as minimum and maximum lengths. The schemas can be converted into real TypeScript types, providing runtime and build-time benefits. You can use the schema.pass method to check if a value matches the schema and retrieve the result with the correct type. Alternatively, you can use a safe parser function that returns a union type indicating success or error.
And as you can notice here, the password does not match with the password type, for instance. The same is for the integer. I can describe my integer part with this part. And the same with the physical code. Then we have to use a function to convert the password to a specific branded type. And the same is for the integer. And at the end, the result is this one. So I have already implemented the password for you, but you can trust in me. Okay. As you can notice, I create a lot of boiler code. How I can reduce this part.
Basically, we can use a library that are schema-based validation. What does it mean? There are specific libraries, for instance, like Zod, that allow you to create a JavaScript code that can work at runtime. Okay? But you can also convert it in a TypeScript type. What is all this kind of stuff? Basically, you can create a password schema that starts from Z. Z is an object exposed by Zod. Well, you can say my schema is a string that has a minimum of 8, a maximum of 32, and you can create also your specific business rule. You can do the same with the integer part, so integer is a brand type integer. You can use the brand type also here. And you can do the same with the physical code. And you can convert, if you want, your schema in a real TypeScript code, in a real TypeScript type, so you can have both the benefit at runtime and build time because basically, if you create a type, when you build your TypeScript code, this type disappears, and at runtime, you don't have the guarantee that your types are still alive also in that moment. In this way, you can also check this at runtime, if you want. You can do this using two different methods. The schema exposed for you, two different methods, a schema.pass method, that basically is like an asset function that returns the result with the right type, so you can pass the schema, like in this case, the value, and check if the value is of this type, you will save, for instance, the password, brand type. As you can notice here, password is a string. I pass through this schema, check the schema, and have the result is this one. If your object doesn't meet the condition, you will save an error.
Also, you can use a sort of type guard using a safe parser that returns a union type where the success is true and contains the data with the type of your object, or successful with the error. What does it mean? It's the same. You can create your safe parser function that accepts the schema and the value.
9. Validating Types and Migrating to TypeScript
You can use schema.safeparser to validate different types of data and ensure type safety at runtime. Custom validations can be created using the refine function. Migrating from a large JavaScript code base to TypeScript can be a challenging process, but it's best to start with small steps and gradually increase the strictness of the TypeScript configuration. It may take time, but it's a worthwhile investment in code quality.
You can call schema.safeparser, and, basically, as you can notice here, this is a branded type of a safe result of the branded type. What does it mean? You can use it and, in this case, if the success is true, you have the password equal to success true, and data is a branded type of password, or, if it's false, you have the type equal to success false, and the error. The same is for the integer and the same is for the parser. You can validate whatever you want, string, integer, number, object, whatever you want.
Also, you can create your custom validation if you want using the refine. This helps you to guarantee your types also at runtime because, yes, it's fantastic to put the number type or the same type, but, at runtime, we don't have any guarantee that this type is still alive, especially if your backend or your frontend send whatever they want in your API or in your UI. So, you can prevent to show, I don't know, not a number or stuff like this. With that, I completed my presentation. I hope you enjoyed it. This is the QR code for the presentation, but I suppose you love more this QR code that contains the code. Last but not least, if you want, in my DevTools account, you can find some blog posts about TypeScript, also in my YouTube channel. These are my contacts. Twitter and LinkedIn are free, so if you want to drop me a message, you can drop me whatever you want. If you want to subscribe to my YouTube channel and my DevTools account, you are free. I hope you enjoy, and thank you so much. Thank you.
The top question is, yes, what are your approaches to migrating from a large JavaScript code base to TypeScript, or to something else? Okay. This is a cool question, and what I tell you is, it's a nightmare. So I won't be honest. It's a nightmare. Yes. But what you can do is try to do small steps. This is the only advice that you can give you. And maybe you can start from having a TypeScript configuration, I can say, less strict. And then... I definitely don't tread strict on it if you're moving on. You can have the strict part in your first step, but move step by step, and do this. This can take a year. So don't... I think, yeah, if you put a large AS in a TypeScript config, you can just move things over as you work on them, right? Sometimes it's a never-ending story.
Understanding TypeScript Documentation
The TypeScript documentation may be more geared towards advanced users, making it challenging for newcomers to find answers. This could be because many people approaching TypeScript are unfamiliar with it, and there is a learning curve involved. TypeScript is not just JavaScript; it's a different part of JavaScript. It might be helpful to write beginner-level articles to explain TypeScript better.
Yeah. Yeah. Totally. Ah, I did that. Right. Next question. I meant to do that. There we go. Quick question. Do you think the TypeScript documentation is more geared towards advanced users rather than newcomers? And if you agree with that, why do you think that might be? Yep. Yep. Unfortunately, yep. Sometimes to go in the advanced part, it's difficult to find also answers sometimes. In some specific case. I think this is because many people that approach on TypeScript are not very familiar with it, and they try to bring all the developer inside of the environment at the end. I mean, it's just a learning curve, isn't it, as well? Yeah. Yeah. Like, it's not JavaScript as well. It's more than that. Yeah. So it's not JavaScript. I don't know how to explain TypeScript, but it's another part of JavaScript. Yeah. I mean, if you ask that question, you want to help out, like write up some articles for more beginner level kind of TypeScript stuff, if you can explain it better. Yeah.
Oh, that one just jumped to the top there, asking TypeCast good or evil? If you are saying TypeCast is as something, avoid it, please. So evil. I use it just for the demo, because TypeScript is able to understand the type if you use the comma, not the comma, the double point, I don't remember. If you are user, if you are, say, constant user, and you create the object in that moment, TypeScript is able to infer the type directly in the type. And basically, you lose that is a user, but it's a person at the end.
Relying on Object Property-Like Type Guards
Relying on an object property-like type person as a type guard may be fragile. To ensure code flow, create validation at the application's borders. Use Zod for validation in front-end applications when receiving data from APIs or external sources. Using any in your code is your choice, but being strict leads to a cleaner codebase.
Fair enough. And I think we've got time for one more question. I think this one's interesting, given it was, I think it was one of the early examples. Isn't it quite fragile to rely on an object property-like type person as the foundation of a type guard? Yep. Could be. But basically, this is the only solution that we have in this moment in the environment, in the ecosystem. And what I can suggest to you, basically, it's to guarantee all the flow of your code, you have to create validation in your border of the application. For instance, if you are in a front-end application, when you receive data from an API or from the external part, validate it using Zod. So you are sure that the input of your application is hard-like expected during your type C code, and the rest of the code, the flow of the rest of the code, is guaranteed by this assumption at the end. Then, if you are using as any, or any in your code, it's your business. But if you are strict with this kind of stuff, basically, the result is a nice code base at the end. Yeah. Right.
OK. I think that's all the time we've got for questions here. Thank you again, Luca. You're going to head over to the question booth right now, so if you have more questions for Luca, go find him over there. But, yeah, thank you so much. Thank you again.
Comments