Conf42 JavaScript 2022 - Online

Safer TypeScript

Video size:

Abstract

You will learn how to write safer typescript:

  • template literal types
  • permutations and combinations of argument types
  • type inference on function arguments

Summary

  • Jamaica real time feedback into the behavior of your distributed systems. observing changes exceptions errors in real time allows you to respond instantly to get things working again. After typescript 4.8 you are able to use lowercase with regular with common type with general type string. This is an unknown feature, but very useful.
  • So let's create our utility types to accomplish this task. filter property by object and type. It iterates through object and checks whether object value extends some type. How we can write our code more safer we can carry it so we can make a function.
  • Using interfaces in typescript is more safer than using types. It is not a matter of preference whether you want to use interface or type. Mutating when people are using types leads to some runtime errors or production bugs.
  • In previous typescript versions, you were not allowed to compute range of numbers. Currently, after optimization of tail recursion, you are allowed to create up to 1000 numbers. How to create repeated string which matches some pattern?
  • A valid hex value can consist of either three chars or six chars. It is from zero to nine and from a to f, uppercase and lowercase. You need to write a function in Javascript which will validate it. For bit validation, let's just stick with raw string.

Transcript

This transcript was autogenerated. To make changes, submit a PR.
Jamaica real time feedback into the behavior of your distributed systems and observing changes exceptions errors in real time allows you to not only experiment with confidence, but respond instantly to get things working again. Close. Let's start from some easy example which is not documented in typescript documentation, but I have found it very useful. So currently you all know that we have lowercase built in type which will just lowercase your string. For instance. Let's try it. Type test equals lowercase. For instance a so test is equal lowercase a so previously, if you provide lowercase string, it will do nothing. Because string is very general type, it's hard to imagine how we can lowercase any string previously. However, after typescript 4.8 you are able to use lowercase with regular with common type with general type string and you will get an error if the string will be uppercased. For instance, we have our string STR variable which should be only lowercased. Here we have okay, here we have an error because of how it works. Now as for second one also undocumented feature. At least I didn't find in documentation explanation of this behavior. However, you can find some explanation here in this stack overflow answer and in my article in my blog undocumented features. You can find my blog here catchtscom okay, so what about inference prioritization? Just imagine that we have some props, select props which expect value generic which will be used for value and for options. And then we have just, I don't know, any react component select which will safer provided value. So here you see I have provided generic constraint that value should extend string and our props will be select props with this value. Current constraint is that if options, sorry, that value should be one of options. So for instance, if options are red and yellow, value accordingly should be either red or yellow. So this is okay. However, what about this? It is obvious that this component that these props are wrong because value should be either red or yellow and in this case it is green. Why? So currently it just should work as it is because value is just an inference of your props. So if you provided a value green, then it is perfectly fine. So as you might have noticed, value is red or yellow or green. If you want to make sure that value should be only red or yellow, you should just add this small sync boom. This means. This means that prioritization of inference of value is lower. It means that first of all we will infer a value for options property and only then we will infer for value. So once we have inferred red and yellow for options property typescript now is aware that value should be either red or yellow. Apart from that, using intersection with empty object also is interesting because it means that it is non nullable value. It was provided in I don't remember typescript 4.8 or 4.9, sorry, don't remember exactly, but they even replaced non nullable built in utility type with this with intersection of generic ta and empty object. So now, as you see, null is highlighted as never, and if I will provide two it is okay. For instance, ari it is okay. So it was very interesting for me to see that this small tree can be used for both cases and furthermore, least it, at least this behavior is not documented and it is, I would say, very unknown feature, but very useful. So let's go to our next section. Useful patterns how to say I would say it is very popular question on stack overflow this example imagine that we have some object, I don't know, some dictionary doesn't matter. And we want to create a function which expects this object and property which corresponds, for instance to strings. What I mean, I mean, if we have this object where a and c properties are strings, then our function should expect only a or c properties, like we are not allowed to provide b property. So here we should get an error, but this string property is not implemented yet, so there is no inference here at all for now. So let's create our utility types to accomplish this task. So first of all, I have created values this utility type. It is very popular utility type and it just returns us union of all values. What does it mean? Type test equals values type of object oops. So it is string and number because union of all values. If I add it as const, I will get a 42 and c. Yes, just union of all values. Nothing complicated, I would say. As for this one, filter property by object and type. This is also, I would say, popular utility type. It iterates through object and checks whether object value extends some type. For instance, in our case we are expecting here that if object property extends number, we will return this object property. Otherwise we will get never. So we just filtered object properties by type. For instance here I get b only because I have provided a number. If I will provide a string, I will get a and c. So pretty straightforward. More explanation you can find, as I said on my blog. So let's try to implement our function. So here I have provided two generic two generics for type inference. First, one is object, which should be a subtype of record where each property is allowed object, key, string, number, symbol and value is unknown because it doesn't matter for us which value should have this object. And as for key, it is like our most interesting part. We are calling filter property by I provided object and string. It means that I want to filter object keys by string. Now this intersection is very important because without it it will not work. As you see, I have an error here. It says to typescript that hey, this object which is provided by you has a key which corresponds to string. This string and this string should be like the same type. It means that if I will call this object with key, it allows me to use any string prototype method charcoal add includes index off. So as you see, it works for outer world and for inner sorry, not world but scope for function outer scope. So you are allowed here to provide only allowed key and for inner scope of function. For instance, if I provide here a number, so we want a number. C is not allowed anymore because it should be only b because we have only one number property, it is b, so it is allowed us to use b. And here we are allowed to use two exponential to fix to local string. So all number prototype methods. Okay, let's go to another example. Believe me, people are asking on stack overflow once per week why Ri prototype includes doesn't work with tuples, I mean with immutable arras I will not provide you with explanation why you can easily find it. It is very popular. I want to provide you with solution which without type assertion, I mean without using s assertion. So currently if for instance you have some immutable array and you want to check whether it is includes d, you will get an error. So how we can fix it? How we can write our code more safer we can carry it so we can make a function which returns a function. So what does it mean? I have created a function with tuple. I have provided generic parameter list which extends an array of strings. And here I have just provided this type. I have used this rest operator for type inference. So currently nothing complicated. As for the second one function, second one function which is returned function by this remember here we have two functions, is a type guard, what does it mean? It means that it expects us one argument which is prop sorry, which is string, and if it returns true, it means that prop is a part of the list, is the element from the list. Just want to remind you what this syntax means. If we have a tuple type with square bracket notation where we just use general type number, it will return us a union of all type elements. What I mean, I will just provide you with small example, just to have an overview what we are doing here. So type here and one, two. So this is our type. Our type is tuple, which consists of three elements, one, two, three. If I use square bracket notation and we'll type a number, it will return us a union of all elements. Okay, so now when we understand what's going on here and here, we can use our utility function. First of all, I have created includes function. Please be aware that includes is a function, because vistauple returns us a function. It means that includes checks whether prop is one of tuple elements, in our case abc. So here you were not allowed to use like any other string except abc. Here we are allowed to use any string because prop is actually a string. So it might be any string. And here, after we apply if condition, sorry, condition statement str will be a, c and b. Okay, let's proceed. Let's go to our next section. It is safer typescript. It is very easy sync and you need to be aware that using interfaces in typescript is more safer than using types. There are a lot of discussion you can find in Internet that I prefer to use types, I prefer to use interface. Somebody prefer to use anything else? I would say in typescript it is not a matter of preference whether you want to use interface or type. It's a matter of, I would say safety or what I want to say it shouldn't be a preference. It should be a rule that if you have some object with some predefined properties, like here we have an interface animal tag name, we can use generic type here. In this case, it's important that we are using an interface and not type. We should use type for aliases, for some making fun utility types, some iteration, but not for declaring just a shape of object. Why? Just imagine that you have a type animal and you have some function handle record, for instance, which expect us an object where key is a string and value is unknown and this object will be mutated. So imagine you have some generic function which mutates your object. I don't want to say that mutating objects in typescript is very cool idea. However we might have it. So if you have this function and it is perfectly fine that you are mutating object here you can assign any value like 23 or, I don't know, some array of numbers and it is okay, typescript perfectly fine with it. Furthermore, you have an object which is an animal, and you are passing this object to this function and this object will be mutated. Just ask yourself, is it okay that I can mutate any object in my application. For instance, okay, I know you are not mutating your object, but maybe your colleague mutating it. Or maybe when you leave your project and one year later somebody will join your team who likes mutating objects, and they will probably mutate it and it is not okay, that animal is mutated. So if you use here an interface, typescript will disallow you mutating of this object. Because when you write interface, you write sealed. Okay, sealed. It is not typescript keyword, but typescript treats it in this way that this object can be extended, can't be changed. It is like interface. I would say this is very important because I have seen a lot of examples with mutating when people are using types, and believe me, it leads to some runtime errors or production bugs. So it is very easy to remember and very important to use. And what's also important, why this happens? Because interfaces are not indexed by the default. This is the difference between interfaces and types. Types are indexed, interfaces are not. Okay, let's proceed with our next section. It is recursive data structures. In previous typescript versions, you were not allowed to compute typescript range of numbers. You were allowed to create range for four t elements, something like that. Currently, after optimization of tail recursion in a typescript, you are allowed to create up to 1000 numbers. It should be. Let's try nine. Nine, nine, small improvement. So as you see, you have a number range of 100 digits. Why 100? Because we start from zero. Sorry, no, 100, but almost 1000. Okay, this was just for testing. I will explain how it works. Sorry. Here we have a recursive utility type which expects us two arguments. One n corresponds to our count of numbers, and second one is result like our array of numbers. When we call it, we are checking whether our lens of our result length of this tuple extends our expected lens to be created. If yes, we will just return a union of all elements in array. Otherwise we will run compute digit range this utility type with same argument n and with extended result. So every time we will add to result results length. So it starts from empty array. Imagine that n is five. Let's start our first iteration. Result length is zero, extends n false. We will go here, and here we will add result, it is empty array, it will just get rid of it. And second, one result length is zero. However, it will be an array of one element. So next iteration will be result length will be one. This is how we increment. Okay, also very popular question, how to create repeated string which matches some pattern. For instance, we are not allowed to use reg x patterns in typescript template literals, however, and also we are not allowed to create some infinity string type with some repeated pattern. It would be nice to have it in typescript, but currently we don't have it. How we can make it works. For instance, we have some type coordinates. This type corresponds to number which are separated by comma, and the coordinate string is separated but by semicolon. I mean, this is our example, this is our type of coordinates. But what if I want to make it longer? You will get an and here, for instance, I expect this we are getting an error because it's obvious what we can do. We can recursively, we can apply this utility type like apply this algorithm. I don't know what is better to say algorithm or pattern. And we can recursively create up to 50 or 100, I don't remember exactly up to 100 unions of different sizes of coordinates. What I mean please. Here I have created compute coordinates with ten iterations, sorry, no iterations, but with ten elements. So this type allows me to use coordinate where I have only two numbers, then when I have four numbers, then when I have six numbers. So as you might have noticed, I don't have three numbers, five numbers, because it doesn't make sense because our coordinates should be atomic, so it should consist of two numbers which are separated by comma. How does it work? Works very similar, but it only differs how I compute the value which is pushed to our tuple. Here I'm using concat previous it just types last element of my result and concatenates it with new coordinates. So this is how each iteration coordinates made one coordinate more. Here you can find links to articles with more explanation because I understand that it might be hard to understand how it works only from this video. For instance, I prefer reading articles rather than watching video, but some people may prefer watch video. Okay, so once we have finished with this pattern, I believe we can proceed to hex validation. I will show you what I will do at the beginning, so you will be aware what I am talking about. I'm talking about a function which expects us some object where key is a caller name, but in this case it doesn't matter whether it is color name or not. No, and value should be a valid hex value. Valid hex value can consist of either three chars or six chars. Furthermore, these charts should match some pattern. Because you are not allowed to provide just six x, it will be an error. So let's start typing this function step by step. So first of all, I want to compute a range of numbers from zero to nine, which are this number. Please keep in mind that these numbers are only allowed in our hacks, but it also all number which you can use. You are not allowed to use more than that. So currently, as you might have noticed, I just wrapped this range in a template literal string, so each number is a string. Also, I'm allowed to use charts from a to f ABCDef, only six charts and they might be lowercased or uppercase it. So I just used the built in utility type I uppercase. So I have a union of my charts, lowercase it and uppercase it. And this is my final hex value. I mean not hex value, I mean all charts or all digits which are allowed to use in our hex string. It is from zero to nine and from a to f, uppercase and lowercase. So imagine that you need to write a function in Javascript which will validate it. Please forget for a moment. For bit validation for some bit manipulations, let's just stick with raw string. So currently, since our hex value can be either three of six elements, what we need, we need to check whether string is whether string lens is allowed. In order to do that, we need a utility type which will return us a string glance. This is why I have created this simple utility type recursive utility type which iterates through each char and every time adds this chart to accumulator. So at the end we have a tuple which lens is equal to string lens. So in our case this will be three or four. Okay, the next step we should validate whether it is three or six. Let's write very simple validate lens utility type which expects us our string and our expected lens. And here we are checking whether string lens of provided strings extends lens. We will return string. Please keep in mind that always when in this case when it is OC, we are returning string. Not truer or false, just string. It is important to be in mind, otherwise we are returning never. So here if string is ABC and expected lens is two, we will get a never. And here we are getting ABC because expected lens is three and we have three charts. Okay, I hope it's easy to understand. Let's proceed. Here we have validate hacks what this utility types do. It iterates through string like through each char. Here how we are getting each char, and here we have a rest charge. So every time we pick one char and then we are checking whether this char extends our hex value extends our allowed hex value from zero to nine and from a to f, uppercase it and lowercase if yes, we will add this allowed char to our cache here. Otherwise it will be never. So here you can check how it works. ABC each char is allowed so it returns are ABC. Second one, HR is not allowed because it's just not allowed. Okay, what are we doing next? It is very important to understand how it works because currently what we are doing, we are just using all our validators and we are making intersection of them. Why we are making intersection because each validator returns us either string, provided string or never. So in case if it returns string like provided string and intersection of equal strings will just produce this string. Otherwise it will produce never. So you can have, I don't know, 20 validators where each validator returns you a string, either string or never, and just apply these validators like that. Validate something else. It will be easy to read, easy to understand, no more cryptic types. So here imagine you don't know typescript validate hex validate lens. At least we are aware that here we are doing hex validation and lens validation. Here I am using union because string lens might be six or three. Second one is just adding this fancy hash symbol to each property, and then this is how this function works. I use type inference or on function arguments. So I have created three generics. One generic for key object key, second one for object value, and third one is our color map. So color map should correspond to this object. And this is wrapped into my validator and this is how it works. So here you see that if I use invalid hex value, it will return me an error. Otherwise it works as expected. So one more thing that we need to use extra function for type inference. We are not allowed to use just alone object. We need to use this function despite the fact that this function do nothing. So it just returns us dictionary. That's it. Thank you very much for your attention. See ya,
...

Serhii Bilyk

@ catchts.com

Serhii Bilyk's twitter account



Awesome tech events for

Priority access to all content

Video hallway track

Community chat

Exclusive promotions and giveaways