Video Summary and Transcription
Hello, I'm Lucas, a software engineer at Klarna in Sweden. I will be sharing my best tips and tricks with TypeScript. One useful tool is tsconfig-basis, which eliminates the need for copying and pasting the same tsconfig file. Rendered types in TypeScript are powerful for handling different types of strings, and type guards and assertion functions make them even more useful. Enums in TypeScript can be number, string, or const, each with its own advantages and limitations. Lastly, there are new features in PS 5.2 like 'using' for resource disposal and 'unknown' for data protection, as well as a case conversion utility type for interfaces.
1. Introduction to TypeScript and tsconfig-basis
Hello, I'm Lucas. I'm a software engineer here at Klarna in Sweden. I will be presenting the best tips and tricks that I've learned with TypeScript in these past years with you. First thing, over the past few years, I lost how many times I have copied and pasted the same tsconfig over and over again, until I discovered that I could just use tsconfig-basis. It's an official repository that has several tsconfig-basis for you to extend naturally.
Hello, I'm Lucas. I'm a software engineer here at Klarna in Sweden. I'm originally from Brazil and have been coding for the past 11 years and sharing what I learned. So if you like this talk and have any feedback, please reach out to me and my socials, just put the name of the network in elsantos.dev and you'll get there, or just elsantos.dev will also do the trick.
But cutting to the chase, I will be presenting the best tips and tricks that I've learned with TypeScript in these past years with you. And all of this in 10 minutes or less. So, let's go.
First thing, over the past few years, I lost how many times I have copied and pasted the same tsconfig over and over again, until I discovered that I could just use tsconfig-basis. It's an official repository that has several tsconfig-basis for you to extend naturally. And tsconfig supports that through the extends property. So, as you can see, we can use the extends and pass the name of the repo following the configuration that we want to extend. And if you don't like any of those configs, you can just override it by writing it again in the bottom. But this is not only for the tsconfig-basis extension. So, you can actually extend if you have local files, if you want to extend, like in a mono-repo configuration where you need to extend the base file. So, what you can do is you just need to put that path in that file, and it's magic. So, you never have to copy a tsconfig again.
2. Rendered Types and Type Guards
Rendered types in TypeScript are powerful for handling different types of strings. They allow you to mark a string as a specific type, such as an ID, preventing mistakes like passing a name to an ID. However, rendered types require explicit type casting, which can be cumbersome. To overcome this, we have type guards and assertion functions. Type guards allow us to narrow down the type without explicit casting, while assertion functions throw errors if the type is incorrect. This makes rendered types more useful and allows for lightweight guards.
For the second tip, I wanted to something that took me some time to understand how powerful it actually was. I'm talking about rendered types. Normally, you don't see a lot of those in TS Code. That's because they have a very specific use case. And those are massively helpful when you have different types of strings, but still strings, like a uid. You can render type to mark it as an ID, even though it's a string. But it's a string of a certain type, making it impossible to pass a name to an ID by mistake, for example. Or even to pass the wrong type of ID to some function.
However, rendered types can only be converted to explicit type casting, which is bad, so we can make a function to explicitly convert one type into another. But it's still, there's a better way to do this. And I present to you the third survival tip, the type guards and assertion functions. When you use rendered types, they are usually followed by one of those two. Because guards and assertion allows us to narrow down the type without having to explicitly cast it. I can explicitly cast anything to an ID and it's going to work, basically. The trick here is basically to add a new function that will guard the type, and this function is saying that a type is of another type as a return annotation. This way, we can do an if check and actually check the ID is an id with the rejects. But this is not just adding noise, this if there. I don't like it. This is why we have assertion functions. They are declared by saying that the function asserts a parameter of a given type. So, the main difference between one and the other is that the assertion functions don't return anything they throw, while the guards return booleans. So, this way, rendered types can be more useful, and you can also create guards that are lightweight.
3. Enums: Number, String, and Const
For the next tip, Enums. Enums are one of the few structures in TypeScript that produce JS code. You can use number Enums, string Enums, or const Enums. Number Enums ensure matching either a number or its type, while string Enums are easier to read but require conversion for assignment. Const Enums replace calls with values in the final code, but don't standardize things and can't be accessed at runtime.
For the next tip, Enums. The big elephant in the room. While some don't like Enums, I'm particularly fond of them. But you need to know them. So, that is the tip. Know the Enums. So, let's start with the basic number Enum. Enums are one of the few structures in TypeScript that produces JS code. It looks hockey, but it makes sure that you will always be able to match either a number or its type. The problem is that you can use both and it kind of defeats the purpose of enumerating things, right? So, what we can do is that we can use the string Enums which are easier to read, but we cannot directly assign the value it contains to the variable that has its type. So, you need to convert, for example, a user input into a new value if you need to assign both. In my opinion, it's worth it because you standardize the code, however enum types can also be a very good substitute for this. And the final one is a const enum. It does not produce any output because it will replace its calls with its values in the transpiled.js, like magic. So, in this example, all the enum values will be replaced with their enums in the final code. It won't produce any output but also doesn't standardize the things, so there's still magic numbers popping up from nowhere. Also, const enums don't exist in runtime, so you cannot access their keys or values the same way you can do with others. So they're expanded into their underlying values and that's it.
4. Resource Disposal, Unknown Type, Case Conversion
In PS 5.2, a new keyword called 'using' allows explicit control over resource disposal and memory cycle. Implementing 'unknown' protects data usage. Introducing case conversion utility type for snake case and camel case interfaces.
There's no coming back from them. For the next revolution, a new one, something that's pretty brand new. In PS 5.2, there's a new keyword, which is also coming to JS soon, that allows you to explicitly control resource disposal and memory cycle. It's the using keyword. It all starts with the symbol dispose or the async dispose that you put in an object class or anything that accepts a method like that. This method will do the work of disposing the resource and then you just need to declare a variable with async using or just using depending on your object, it's a sync object or not. And this is a very good life-saving tip because you can abstract a lot of the complexity from object lifecycle away from the usage of that object.
For one of our final tips I want to bring you the world of unknown and how we can actually save a lot of lives. In Klarna, we have a lot of places where we deal with legacy systems, especially those that were ported to TypeScript and all the places where people didn't know what to type, there isn't any there and that's very bad. Bad because we are literally disabling TypeScript for those types and we are not inferring anything. There is no types, we all know how bad any is in general, but there is a solution. This solution is unknown. We start implementing unknown and the catch is that everything can be assigned to unknown but unknown cannot be assigned to anything. So this way we protect the data where it's actually used.
And lastly, I wanted to introduce the case conversion utility type using uppercase, capitalized or lowercase. The context for this is that in Klarna we have a lot of other services using snake case and some other services that use camel case. Because of that, we needed to convert API calls and returns to the specific casing and this led to the creation of these duplicated interfaces where one was the snake case and the other one was the camel case. So you don't need to do this, obviously there is like an overcomplicated way of making it generic like I did here, but in this example what I'm creating is a type that takes a string, splits it from snake case using a recursive type and make it to camel case. And then we can use a mapping type to map all the keys from an object to camel case and now we don't need to duplicate our interfaces anymore. And that was it, basically. 10 tips in 10 minutes. So thanks a lot for being here and for listening to this, and if you do have any questions, please join me in my socials and in the websites down here.
Comments