1. Advanced Concepts of TypeScript
Today's talk is about Advanced Concepts of TypeScript. Learn lesser-known TypeScript concepts to improve your developer experience. The first concept is typeguards. Typeguards help TypeScript determine the type of a variable. By using typeguards, you can ensure that TypeScript knows the variable's type and can use the appropriate methods. Another concept is assertions, which allow you to tell the compiler that a certain scenario won't happen again. By using assertions, you can prevent errors and ensure the expected behavior of your code.
Hi everyone, today's talk is about Advanced Concepts of TypeScript. Some lesser-known TypeScript concepts that you can learn and then use it in your day-to-day projects that you can dramatically improve your developer experience and you know, the usage of TypeScript itself, so let's continue.
So the first concept is typeguards. So let's see what it is. So let's say you have a function that takes in any argument type, and if it is of certain type, we do some kind of operation with it. For example, I'm here checking if it's a string, and if it's a string I'm using some string properties and accessing that. Otherwise, it's not a string, then we do it differently. So in this example, I have a string function that checks for the type of the variable and returns that it's a Boolean, it's a true or false. So with this, if you have it like this, then the typescript won't know if the variable is actually a string. So we have this check, it returns true or false, but there is no way typescript knows the variable is string or not. It just knows it's true or false and comes here and doesn't know about the type of the variable, right? So for that, what you can do is you can do some kind of typecode check here, so you can use the is keyword in typescript, and explicitly state that the variable is string as a return type here. So now what happens is, once this check passes, once it returns true, then typescript surely knows that this variable is of type string and it can use all the string methods that it has. So that's the beauty of this typeguard. So let's see an example in the code, actual example. So this is before adding the typeguard. So now if you see, this returns Boolean, this returns Boolean. And once you come here, now the variable is any and even after it returns true, the variable is any. So you don't get all the type checks, you know, at the compile time. So now after we add the typeguard, now here the variable is any, but once this check passes and comes here, now the variable is string. So which means we are telling the typescript that it is a string and you can use all the string methods that it has and it will be easy for us to do all the string operations within this true condition.
So now let's look at the assertions, so this is the way to tell the compiler that this scenario won't happen again. We are asserting it that it won't happen again. So in this function I'm testing if the variable is string or not. So if it's not a string, we throw an error. So if it's a string then there won't be any errors. So we call this function here with the variable passing into it and if it's not a string then there won't be any error.
2. Type Guards in TypeScript
This part explains how type guards work in TypeScript. By using type guards, you can ensure that a variable is of a specific type and use the appropriate methods and properties. Type guards are particularly useful when you want to throw an error if a variable is not of the expected type. This concept is powerful and can improve the developer experience.
Then this console log will happen, so basically after this function execution is done, the variable would be a string and we are asserting that it is a string. So now what happens is, this return type is asserts and once we pass this function call, we know that the variable is a string then we get the variable as string and we can use all the string properties. So this is more powerful if you don't want to do a condition, Boolean condition and you want to throw an error in your example function there when it's not a string, you want to throw an error. So once the error is not thrown then we know for sure, it's a string and use all the string properties. So that's with a type guard.
3. Generics in TypeScript
This part explains the core concept of generics in TypeScript. Generics are essential for creating reusable types and making React components more generic. They allow you to create utility types and work with unions of keys. The keyof keyword in TypeScript returns all the keys of an object, which can then be used to assign values to variables. By understanding generics, you can parse the keys of an object and return all the types associated with it.
So before we get into that, I want to show you a few examples before we start the generics. So let's look at a couple of keywords in TypeScript. So let's say this is a person object with name, age, email, and student Boolean. And once you assign the person, this would be the values that you can give to a variable. And this keyof is a TypeScript keyword which returns all the keys of an object. So here all the keys are name, age, email, and student. So it will return all the union of all the keys. So once you assign that key to a variable, all you can see is all the value that you can assign to this variable is all the keys of the person object. So this is more powerful. You will be using it in all the upcoming examples.
So basically it will return all the unions. So you return age, name, email, and student, and you can assign values to it. So this one is similar to how you access a normal object. So you define the object, and you call the key of the object within it, and it will return the value. So in here, since it's a type, it won't return the value, but it will return the type of the key. So this object key. So the type of name is string. This will return string. This can also take in union of keys. So here I'm giving name, which is also a key, and age is also a key. So with these two, this will return both string and number, so because the name type is string, and age type is number. So it will return union of these two. So basically, so now with this, we can parse in the key of person also. So key of person is basically union of all the keys. Now we can parse in that, and return all the types that are part of that particular object. So this object has keys with string, number, and Boolean. That's what it will return.
4. Core Concepts of Generics
This part covers the core concepts of generics, including the keyof keyword and using literals to save individual values as types. These concepts are essential for upcoming lessons on Generics.
5. Using State Machine and Generics
Now let's look at a simple example of using a state machine. By using the keyof property and generics, we can restrict the initial state to only be A or B. This ensures that we get errors at compile time and resolve them before going to production.
Okay. Now let's look at a simple example of using a state machine. So state machine is something that has a bunch of states and also initial state to start with. So if you want to type this function, primarily what you will do is you will declare a type or interface and define the states.
This works fine but even we can restrict it more so that it's when we are using the make state it's more easier for us to implement it and use it. So what I mean by that is, since the state is, where properties are A and B, the initial state can only be A and B. It cannot be C, it cannot be D, right? Because the initial state is either of the states that is being defined.
So what you can do is, you can use the keyof property that we just, keyof keyword that we just discussed and we use generics like this. So we define, so we already had a config, we are defining the generic like this. This can be any type and any type. So whatever you pass in, that is passing to the state and the initial value would be keyof state. Basically, all the properties are the keys of the state that can be the initial value. And this is how you pass in a function. So you define the generic in the angular brackets here. And also once you define it, you can pass it down to the config type. So once you have that, now only possible values are A and B. So let's see how it works.
6. Using Generics to Restrict Variable Values
By using generics, we can restrict the possible values of a variable and catch errors at compile time. This ensures that only valid values are used and prevents runtime errors. Generics are a powerful tool for improving code quality and reducing bugs.
Okay. So since I have a generic written here, if I just put the double quotes, the only possible value are A and B. If I give C, there will be an error thrown that's saying C is not part of A and B. That's because of the keyword we use. If we just have it as string, now anything is possible. You can just give C, then you'll be getting a type error in the runtime, because the C is not part of the available states. So with this restriction, we can be sure that we get the errors in the compile time and resolve it before going to the production. That's how generics can be used to restrict more types and then make the errors easily identifiable.
7. Making a List Component More Generic with Generics
This part discusses how to make a list component more generic using generics in TypeScript. By defining a generic prop for items and another prop for rendering, the list component can be customized to render different types of items based on the color prop. The rendering logic and item definition are moved to the calling component, allowing for flexibility in rendering and item types. This use of generics enhances the reusability and flexibility of the list component.
So that's one example of generics. Let's take a look at another example with a React component. So this is a simple list component that we have. And this list takes in an item's prop and looks through it and renders the ID and name. So if you want to type it, that's what you usually do. You just list a prop type and the item's signature here, ID and name, and it's an array.
So similar to the functions, we define the generic in the square, in angular brackets, and give the props and pass the item to it. So now we are just looping through and rendering the item here. But the rendering logic and the item definition would be part of the calling component. So where the component being called. So we are calling it in the form component here. So if you see here, we are defining the item's signature and this signature would be inferred in the render item. So whatever you pass, whatever signature you pass in the first items array, that signature will be maintained in the render item because we have the same t item type in the items render item argument itself. So if you see the example here, so here I have ID name and age. the items would be of the type ID name and age. So if I give this student true, now this will be, this will also be added here. So whatever the, so now the control is given to the calling component. So it's not in the declaration now, when the component is being created now. Now the control is given to the calling component. So now the caller can decide how it want to render and what are the items you want. It can be an item of students, it can be item of, you know, grocery lists and all those things, any kind of items. Now the list component is more generic by the help of the generic types.
8. Utility Types in TypeScript
Now the list component is more generic by the help of the generic types. Now let's look at some utility types. TypeScript provides utility types like pick, omit, and partial. Pick allows you to select specific keys from a type, omit removes specific keys, and partial makes all keys optional.
Now the list component is more generic by the help of the generic types. Okay. Now let's look at some utility types.
So these are all, now, so previously we saw generic components and generic functions. Now the types can also be generic and basically can be used to create other, transform some particular type to other type and return that type. So basically, basically transformation of types. That's what the utility types are.
So there are a bunch of utility types built-in available utility types in TypeScript. So let's look at them one by one. So let's say we have a to-do object with title, description, and completer. And we want to define another type, another type to-do preview which only has title and completer. It doesn't have to have the description because we are just previewing the to-do, let's say.
So for that, TypeScript provides a utility type called pick, which basically takes in the base type, which is to-do here and pass in different keys. So here, the keys are title, description, and completer. So I'm passing in title and completer. So we are picking these two keys from this type and then running it here. So now this preview will have only title and completer. It won't have description. So this is a powerful utility that we can use. So this is similar to, we have a, similarly, we have omit utility type. This is just the opposite of pick. It just omits whatever the key that you pass in. But here, I'm just going to omit the description. So now it'll be title and completer. So these two are two powerful utilities that are used quite often. And there's also one more utility that is used quite often is the partial. So here, all the properties are required, title, description, and everything is required here. So what if I want to make a partial basically, make all the keys optional. So use the partial. So once you pass in the to-do, here you'll see all the properties have been marked as optional.
9. Partial, Read-Only, Parameters, and Return Types
This part covers the concepts of partial, read-only, parameters, and return types in TypeScript. Partial allows optional fields in a function, while read-only makes properties unmodifiable. Parameters and return types can be obtained using utility types. The upcoming examples will demonstrate how to create custom utility types using these concepts.
So this optional to-do can be used in place of update, so if you want to update a particular to-do item, just the title field or just the completed field and rest of the other feeds are optional, you can just use this in the function.
So that's about the partial. Now let's do it read only. So now this to-do type can be modifiable. So if you assign it to a variable, that can be modified, you can modify title, description and competitor. But if you want it to be read only and unmutable, then you can use read only. Now this will mark all the properties are read only and can be assigned only once and cannot be modified. That's read only.
And now let's look at couple of things, parameters and return types. So let's say this is a function, right? So I'm getting a type of the function and storing it here using the type of keyword. Now I'm wanna get the type of the arguments that are being passed to this function. So using this parameters utility type you can get that. So it will return a tuple of type argument types. So here we have to do and it's up to do type and we have updated to do fields this of partial to do. And same way we can also get the return type using the return type utility. Now we will return, so here we are returning the exact to do itself. So we are getting title, description and computer. So this is more powerful. These utilities are more powerful. In the coming examples I'll show you how you can create your own utility type using these two concepts. Before that, custom utility types. Before that we want to see, we want to learn two concepts. One is the map types and conditional types. So first let's look at map types. Okay so this basically is a syntax for map types. So what it does is, we use the same key of keyword to get all the keys of a particular object type and this is like a iterator. So if you see foreign loop, right? So it will take all the keys individually and loop through it and then return something else. So that's what this, you can think of it as the same analogy. So this in, it's like a iterator. So this, all the individual key of the object will be stored in key, stored in K and you can use this K anywhere in the, in the right-hand side of the colon.
10. Making Objects Read Only and Partial
So let's take an example. We have a person and want to make it read only. By using the key of person, we can loop through each key and return its type. To make it read only, we add the read only modifier to the map type, making all the keys read only. The same concept applies to partial, where we loop through the keys and make them optional. To make them required, we use the minus modifier. The same applies to non read only, where we use the minus modifier to make all the keys required.
So let's take an example. So we saw read only UDT type, right? Let's see, let's see how it is implemented. So we have a person and I want to make this person read only, read only person. How I'll do that is the same thing. I'll use K in key of person. So this will return all the key of this person type and I'll loop through one by one. And what I'll do is, I'll just return whatever the type it has. So I'm not modifying anything. So this will just, if I just do this and not add read only, it will just return the same thing that is in the person. So basically I'm just looping through it and then sending, so this key, person of key will just return string, person of age will just return number and since and so on. Now I'm just want to make it as read only, so I'll just add this modifier read only to this map type and this will have add read only to all the properties of the key, all the keys basically. So now this is read only. So once I assign it, I can't change it, change the age. So it will throw it as read only. Same thing for partial. So we are looping through it and saving all the keys individually and then making it as optional. And then, you know, this one is unmodified. We just return whatever the type is here. So now all the person keys are partial, optional basically. And if you want to make this as required, right? So now it's partial. So what if you want to make it as required? So we'll just use the minus in front of the modifier. So these are all modifiers. So the question mark, is read only, these are all called modifiers. So if we add a minus then it would be negating the initial purpose. So now we are passing partial, this is optional. Now we are using minus. Now this will be, all will be required. Same thing goes for non read only. So we already had read only person, this is read only. And if we add the minus in front of read only, now this would be, all would be required.
11. Understanding Conditional Types in TypeScript
Now let's dive into conditional types. They are similar to ternary operators and are used to return non-empty types from a union of types. By using the extends keyword and the ternary operator syntax, we can check if a type extends null or undefined and return the appropriate result. Let's break down how it works. We start with a union type and individually check each type. If a type extends null or undefined, it returns never. Otherwise, it returns the original type. This allows us to filter out null and undefined types from a union and return only the non-empty types.
I mean, all will be non read only. So this is for map types.
Now it's conditional types. So conditional types. Okay. So this conditional types, is basically similar to a ternary operator. So, this is non empty.
Let's make, so the example I wanna show you here is, if we pass in, this is basically a utility that we wanna create using conditional types. What it does is, what it should do is, if we pass in multiple union of types, it would return the non empty types. So non empty types are non undefined and null types. So if it is string, Boolean, or number, then it will be returned, otherwise it won't be returned. So now, if the result will be string, if I just have Boolean, now the result would be string and Boolean. So basically non-empty types.
So for that, we use the extends keyword and use the question mark and colon, ternary operator syntax. So what it does is, whatever the union that we pass in, that would be in T, and we're checking if T extends, null, or undefined. So basically what it means is, it will see if the type that we pass in is null or undefined. If it is null or undefined, then it will return never, otherwise, if it's not, if it's non-null, then it will return whatever we pass in. String, Boolean, or number. So let's break down how it works. So this union type we have, this would be expanded something like this. So individually it'll be called. It's equal to Boolean for now. And after this, the string will be passed here. So the T would be string. So this would be extended basically. So in place of T, we have string, extends, null or undefined, never. Instead of T, we have string. Same goes for undefined, same goes for null. Now this check will happen. So string extends null or undefined, so no, it doesn't extend, so it will return string here.
12. Understanding Conditional Types in TypeScript
Undefined extends null or undefined, then it will return never. And same thing, null extends null or undefined, yeah, it extends, so it will return never. If it is an AND, right, if it's, let's say if it is intersection, now everything will be line. That's how we get non-empty types. This is the turn-in syntax that we have for conditional types.
Now this here, undefined extends null or undefined, yes. Undefined extends null or undefined, then it will return never. And same thing, null extends null or undefined, yeah, it extends, so it will return never. So the one cool thing about TypeScript is, of the never keyword is, it would be ignored by TypeScript if it is a union. So this is union. So now, the never will be ignored, and only the string will be written. So that's how we get string here. But if it is an AND, right, if it's, let's say if it is intersection, now everything will be line. It's similar to the AND operator and OR operator. Similar to zero or ones, right? So zero, zero, zero, and one is, AND one is zero, and one and zero OR one is one. That's what it means. So that's how we get non-empty types. So basically, this is the turn-in syntax that we have for conditional types. So let's see one other example.
13. Excluding Types with Conditional Types
We can use conditional types to exclude specific types from a union. By declaring an exclude type and passing in the types we want to ignore, we can eliminate them from the resulting type. Conditional types allow us to check if the base type extends the exclude type and return the appropriate result. This powerful feature improves type safety and ensures that only the desired types are included.
So we have a list of statuses here. We want to exclude, so we declare an exclude type, it will type mistakes in the base type and a union of exclude types. So that will exclude whatever we pass in. Similar to omit, but this is for union types. So omit is for objects, and this is for union types. So we pass in the status, basically this entire thing, and we pass in the types that we want, the literal types that we want to ignore. So these two. So now this will return pending progression status. It will eliminate the negative statuses. So basically, it's the same syntax. So we check if the base type extends the exclude type. If it extends, then we return nothing. We will never basically, and if it's not in this possible types, then we return whatever the base type is. So it will look through individually, so it will check for pending and warning, pending and error, in progress warning, in progress error, all the condition would be checked, and it would be returned accordingly. So that's conditional types.
14. Combining Map Types and Conditional Types
Now let's combine map types and conditional types to create a custom util type. We want to create a type similar to omit, but with a different functionality. Instead of providing the exact keys to omit, we want to specify the types of keys we want to eliminate. By using the map type, we can loop through the base object and check the type of each key. If the type extends the type to remove, we return the key itself. Otherwise, we return never. This allows us to convert the type to the actual keys and create the desired custom util type.
Now let's combine these two, map types and conditional types, and create a custom util type. So the util type that you want to create is similar to omit, but a little different. So omit takes in the key of, so this is what omit does. Type omit keys, omit person, comma, so it will take in all the person keys, let's say h, and it'll omit all these things and just return whatever the left, whatever the keys are left. Now what you want is, instead of giving the person, instead of giving the keys as a second argument, we wanna give the types that we wanna eliminate. So we don't wanna give the exact name of the key, but we wanna give the type of the key that you wanna eliminate. So we wanna eliminate here, Boolean and numbers, we don't want this, and we don't want this, we just want name and email. So once we give that it should return the string variables only. So how we do that? String keys only, so how we do that? So we just use the same signature that we have for omit, so omit takes in the main object as a base, and we also take in all the union types. And we use the same omit but with a different type, so basically omit expects keys as a second argument, right, that's what it expects, keys as second argument. But we are sending number, and we are sending that actual type as a second argument. So we want to convert this one to the number, we want to convert the type to the actual keys. So that's what is being done here. So we are using the map type to loop through the base object and get all the keys, and what I'm checking is checking the type. So this main object of key will return name or number based on what the key is, if it is name, then it's string. So it will be string extends the type to remove is number or Boolean, let's say string extends Boolean, it's not right, so it's not extend Boolean. Then we return the key itself, so we return name. So for name, it's a string, if it's extends, we return that, so in this example, let's say this is a string extends Boolean, it's not extends Boolean, so it'll be never, so that's what it's being here, and next is age. So age, a type of age is number, so this is number extends number or Boolean, yeah it extends, then it will return the number, the key itself, so it will be age. So I'm returning key, I'm not returning the type, I'm just returning the key directly. Again, email, the type of email is string, they will be never, then is student, so this one, type of is student is part of Boolean or number, yeah, then it'll return the type, basically is student, it'll return the key, basically is student, so this is what it returns after this is being executed. Again, now we want, we don't want this object. What we want is we want name and age to pass, right? So how we pass that? With this one, so if you remember this example we saw, this person can take in, this type, can take in keys of person, basically union of all the keys, right? So it'll return all the type of those. So if I pass in, let's say this is Boolean, right? It'll also return name, number and Boolean. What are all the type of these keys that'll be written. So in our example, it's a little bit different. It'll return the same thing, but what type we have? We have all the literal types, we have never, we have age as literal type, and never. And again, student is a literal type. So that's what being written. So this key of this is like this, and it will return all the actual types that's being given.
15. Custom Utility Types and Infer Keyword
Actual types are being used to omit specific types and create custom utility types. The infer keyword extracts types and stores them in a different type for use in conditional types. We can pass a function type to check if it extends a specific function and infer the return type. These concepts enhance the power and flexibility of TypeScript's type system.
Actual types that's being in, so it will be after this, it will be never, age, never, and your student. Okay, that's what it will return. Now, if you know, in the previous example I saw, I told never can be, it would be ignored. So the age and the student would be left, and that's what being passed to omit. So omit, what we pass to omit? The base type and the age and the student. So if you pass that, then it will omit those two types, and will return, just name and email. So that's what is being done. So to recap, we created omit kind of utility type, but with actual types, not the keys, and it would omit the same way how it omits the actual keys. So this is more powerful if you combine with, that's how it's powerful to combine with the key map types and the condition types. So you can do much more like these utility types on your own.
Now let's look at infer keyword. So infer is also used along with the conditional types. Basically infer extracts out the types and store it in a different type and you can use it in either of the conditions that we have in the conditional types. So let's assume this is the function that returns name and age. And this is how the return type works. So we saw the return type, built-in utility type return type So this is how it's been implemented. So this is just the function signature. So consider, so basically you wanna pass in, so we wanna pass in the type of the function, right? So basically this t is basically a function. So we wanna check if this t is of the function type. So that's what this is. So for now consider this as any. Right, so what the function is, it function has arguments, any number of arguments of any type and it also returns any type of value. So that's how it done is. But while we are returning it, we are also inferring the value. So whatever the value being returned, we are inferring the type of that value and storing it in R. And we are returning it in the true condition basically. So if t extends this function, then we are returning the return type of the function. Otherwise we are not returning anything. We are taking never.
16. Infer and Template Return Types
We can infer the return type of a function by using the type inference feature of TypeScript. By passing the function type, TypeScript can infer the return type based on the function's implementation. Similarly, we can infer the prop types of a component by using the component type utility in React. By checking if the passed property is of the component type, we can infer the prop type. This allows us to get all the prop types of a component. Lastly, let's explore template return types, which are used to define the return type of a function component. By specifying the input types and using index signatures, we can create more precise type definitions for our components.
We are taking never. So once we pass the type of the function, if it's a function, yeah, it's a function, getUser is a function and it will then further return type, so it will infer this and store it in R and we are returning the R in the true condition. Then if it's a function, then we return the return type of the function. So we'll return name and age. That's how the return type is being calculated.
So similarly, we can calculate the prop types of a component. So let's say this is a functional component and this is the prop type, name and age. And that's how the component is defined. So let's write it a type, all prop type, and which takes in a type p and we are checking for if this p is of component type. So React has this component type utility to check if it's a component. So instead of we defining the function, React also has its own component type checker. So we are checking that and while checking it, so this is how we pass the function type in React. So we are inferring the passed property. So similar way we inferred the return type, we are now inferring the prop type. So we're inferring that and saving it in p and if this condition is satisfied, you're returning the prop type otherwise, you are never. So now if you call it with the component, we just get all the prop types of the component. So this is about infer.
Okay. So on the final thing, this is a lot more exciting when I just learned this. So let's see what it is. Template return types. Okay, I think I have a slide also. Okay. So let's say we have a function. It's a react component, it is a tag component. And it takes in color, label, margin, and pass it down to the, and then render the tag of the label. So how would you type this? Let's say we have also have a status colors, which is a list of colors that we have. And the colors can contain either of these colors. So remember, ascons, we'll make this as read-only. And we can use this index signature, number index signature to get the individual colors.
17. Extending Color and Restricting Margins
We can extend the color property to include any hash colors. The margin can be any string, but we want to restrict it to actual spacing properties like 12 pixel, 12 rem, one rem, two em. We use string templates to enforce these restrictions.
And there can be set individual types, literal types, and can be set to the color property. So this works. But what if we want to, and the margin also string. So what if you want to extend the color, not only to these four colors, but also any hash colors, right? How would you do that? So we want to have these four, and you want to have autocomplete for these four. And we also need to allow the users to give any other colors other than these four. So, and this margin is now a string. It can have any string, it can have just have green also. But we want to restrict the margins to have the actual spacing properties, right? Like 12 pixel, 12 rem, one rem, two em, and those spacing values. So how would you restrict that? So this is how we use the string template to restrict it. So this one we already had, we already had the four built-in, four status colors. But it can also have any string that contains, that starts with hash. So all the colors will start with hash. So all the string that we want to send to this color prop should also start with hash. That's how we restrict the colors.
18. Restricting Margins with String Templates
Margins in CSS can be restricted using string templates in TypeScript. By defining the acceptable units and values within the backticks, you can enforce restrictions on the margin values. For example, margins should start with a number and end with pixel, rem, or em. If a value does not meet these requirements, TypeScript will throw an error. This feature enhances the developer experience by providing autocomplete suggestions and compile-time warnings for type-checking.
And what about the margins, right? So margins end with pixel, rem, or em. Those are the spacing units. So that's how it ends with. And what it starts with? It starts with a number. So it's like 12 pixel, one rem, two rem, like that. So it starts with a number. So within this backticks, you can give what you want to give. I want to read number, and I also want it to end with pixel, rem, or em. So now let's look at the example. It would be in, yeah, this one. So, now once you have that, let's say color. So if I do this, I'll get the autocomplete for the status colors, because that's all it's what we get. We can give either of one colors, or we can just give any color just starting with hash. But if you give something else that's not starting with hash, then we'll throw an error, saying it's not assignable to the property color. Same thing goes for margins. So margins is optional, that's why it's not throwing any errors. But once you start giving that, the value that you can give is, let's say, dot pixel. That you can give, because that's the accepted value. But if you try to give something, some A in between the friend, it'll throw an error saying it's not of type, because it should start with a number. And it ends with pixel, so if you give something else, then it will say, no, it doesn't end with that. So that's how powerful the string templates are, so you can restrict even more, so that when you are using the component, let's say, when you're using the tag component, then the user knows, okay, this is the exact value that this prop requires to make the component work. So the developer experience is even more improved with all these restrictions and compile-time warnings that you get doing all these type-checkings.