Generics: The most intimidating TypeScript feature

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
what's up Wizards I have a treat for you today we are going to be focusing on 10 tips to make you a master of typescript generics when you're done with this video you will understand a whole lot more about generics and you'll be confident enough to use them in your code if you don't know what generics are if you don't know how to use them then you are missing out they give you the power to make abstractions to make your code a lot more dry to make sure that you're not needing any excess type annotations typescript without generics would be absolutely awful even if you've never written one yourself you've probably used thousands of them so let's get going tip number one we start here with what actually is a generic generics actually refer to a couple of different patterns and the first pattern is here we can take this my generic type and currently it's just got data as any we can actually pass a type to it we can declare that this type now instead of just being a type it's actually a kind of type function that takes an arguments and now my generic type is actually going to be erroring and going to say generic type my generic type requires one type argument so we can actually pass an argument to it we can say first name String let's say and now we can use this T data anywhere we want to inside this type declaration so we can say data is T data and now example one you can see if we hover over it we have example one data first name String so we can now use my generic type to basically represent a lot of different things if we want to so we can have example two has data string if we change this to a number you end up with data number in there so this pattern lets you create type helpers type functions that you can create other types out of that's what's called a generic type tip number two you can create functions with a type helper kind of mapped over the top currently we have this make fetch function where we pass in a URL and we end up with basically the return type of this fetch which is going to be any here because this returns any so our resulting here is typed as any that's kind of annoying what we'd really like to do is actually type it with what we get back let's say we get a first name string and a last name String back now our result is typed in the correct way but it's not as cool as it could be we're having to mash on this extra type annotation here surely there should be a way that we can do this kind of inside our function well there is we can make t data inside here this little syntax basically maps on a type argument onto your function then we can declare the return type as promise to data because this is kind of it's returning a promise this returns promise any so now what we can do is we can move this declaration and actually pass this as a type argument to make fetch so this syntax here we're now passing two arguments one of them is a runtime argument and one of them is a type argument up here and this type argument tells res what it's supposed to be because this is what's getting typed in promise T data so this is now a generic function and a generic function is just a normal function with a type helper mapped on top of it tip number three there are actually way more generic functions in the language than you expect are going to be there for instance we have this set kind of class here which is built into JavaScript and ideally we want this to just be a set of numbers we can add a number here but we can also add a string and we kind of want this to error on the type level you notice that set is being inferred here as set unknown and this little thing here should give you a clue as to what's needed we can actually hover over set here where it's a set Constructor and you can see these are being inferred as unknown if we command click into it we can see that set here it looks like a generic type is being mapped over so we can actually pass in the argument that we want even though we're not passing any runtime arguments here we can pass in a type argument and now the only thing that we can pass to our set is a number there are lots of parts of JavaScript where you're going to need to pass in these type arguments into generic functions that you weren't even aware of especially if you're using something like react then use State use ref use reducer like a lot of the hooks and a lot of the other parts too create context too will all need you to pass in a type argument along with any runtime arguments tip number four here we have a function called add ID to objects where what we're doing is we're passing in this kind of object here and we end up with a result which adds an ID to it so it adds it kind of on the type level here and it adds it here too I can actually remove this I think in typescript will just sort of still cleverly and further results but there's something ugly about what we're doing here which is we're passing a type argument here which looks like it has exactly the same structure as the runtime argument that we're passing it would be really cool if one could like infer from the other well it turns out that what we can do is actually remove this whole type annotation and everything still seems to work that's because here we've got this T here so we can actually change this to T object if we want to like the name actually doesn't matter and then what's Happening Here is because we're not passing a type argument then typescript is going to look in the runtime arguments to see if it can infer anything from it we hover over add ID to object you can see that the type argument there is being inferred as first name String last name String so even though we've got this kind of slightly complicated generic signature up here we can actually just use it without even thinking about passing a type argument or anything there and we still get a really smart result meaning that we get autocomplete dot ID right there this is a central idea in understanding generics understanding that they can actually infer the type arguments you pass them without actually needing to pass any type arguments this is the source of a lot of the magic that you can do with generics tip number five here we're going back to the type World leaving the function a minute we have this get promise return type T equals awaited return type T that's pretty confusing so we have this awaited thing here what it does is it basically takes something that you pass it and it's like you call a weight on it so if we pass it a promise string inside there then it's going to return us the string if we pass it promise number instead then it's going to return the number return type does the same thing but it does it for functions so we can pass it a function that returns a string and we'll get a string and a function that returns a number and we'll get a number if you combine the two you get a type helper that takes in a function that returns a promise Returns the return type of that and then gets the awaited version of that so in other words results here is typed as whatever in this little box here so first name last name if we add ID to it then we're going to get that inside our thing there too but we've got an error here the error looks like this type T does not satisfy the construct trained args any to any the reason this is erroring is because return type actually only allows you to pass certain things to it so if I say type result 2 equals return type and I'll pass it as string then that's going to start yelling at me that's because if we look at the Declaration of return type here then there's actually this little extends Clause just next to the T you can see here's the type argument and here is a constraint on the type argument what this constraint is saying is that we can only pass in basically any function into here because getting the return type of a string doesn't make sense because strings can't be called they don't have return types so what we can do to make typescript happy here is we can actually say this T needs to extend the same thing as return type does so what we finally get is that we can now only pass in functions into our get promise return type here and it's saying that type string does not satisfy this constraint if we wanted to constrain it to something else we could say t extend string and of course this breaks everything because we can't we now can only pass strings to it or we could say Boolean or number or whatever you can basically put any type in here these constraints are really useful because they allow you to make sure that you're only getting certain things into your type helper or as we'll see in a second only certain things pass to the type arguments of a function tip number six this is a really complicated one we're looking at a function called get key with highest value we pass in an object here and we're returning an object with the key which is one of the keys of the object and a value of Number the idea here is if we pass in the like a b c with each with differing numbers it's going to grab the max out of that and grab basically the result here too which is going to be C so our types actually look correct we're doing a pretty good job here we're getting key which is a or b or c and finally we're grabbing the value here too which is result.value which is always going to be a number but we have some errors inside the function of object.keys it looks like like can only be passed in as oh it needs an extends thingy constraint this means that we need to actually constrain this T object because you can't call object.keys on a number you can't call it on a string so we actually need to constrain it to be something that object.keys can handle and we also know that the thing we want to pass in is an object where the keys are strings and the values are numbers to do that I can actually say extends a record with string and number a record is a type helper that basically lets you gives you like an object where you have Dynamic keys of strings and then you specify the value that you want there too what this now means is when we call this function we can't pass in a Boolean to this anymore because this is not assignable to type number in other words we're still getting the inference from what we pass in here we're still getting ABC number but we're constrained by what we want to pass in when you have a generic function you're usually going to want to put a constraint on this gives you control over what the users of your function can pass in and it usually means that inside your function you need to do less logic tip number seven here we have a typed object Keys function what this does is it basically takes in an object that we have here and then returns an array with the keys kind of like mapped into an array there by default object Keys is actually returns an array of strings and so this is actually incompatible with the return type that we've specified here how do you get around this because typescript seems to think it should be one way and you think it should be another in this case you know better than typescript so what you can do is you can actually do this you can say as array key of T object now we get the same exact results but typescript has stopped yelling at us you might be looking at this and thinking oh no you shouldn't really use as like as is basically a way to assert that you know more than typescript but in this case it's actually what you need to do sometimes inside generic functions you know better than typescript typescript is going to lose its way in really complicated generic signatures so a lot of libraries that use this type of syntax will slap on and as any just to make sure that the return type matches the thing that you're getting back and this is absolutely fine to do inside a highly generic function tip number eight here we have a function called get value and what this does is it takes in an object as its first parameter and then a key is its second parameter and then it's going to return the object key here so when you have a problem like this your first thought should be what do I need to capture in the type arguments in order to do everything I need to do at the type level it's pretty obvious where going to need to capture this right we're going to need to grab the object that's being passed in because they could pass anything in let's grab that inside T obj here and stick it just there so now if we look at get value then we can see that the type argument is being inferred properly now this key here we know that it's going to need to be something that can index into T obj So a good candidate here would be key of T obj and this looks pretty good actually because we now get autocomplete on side A and B here if we add another value to this let's say C true then we're going to get autocomplete there too this is a great lesson in itself that you can have different parameters that each rely on the same generic but this isn't quite good enough because the result isn't actually the thing that we're getting from the index the result is actually a union of all of these types here what that indicates to me is that c isn't being treated as C on the type level it's actually being treated as a or b or c C and you can actually see that here get value C or a or b return string or number or Boolean we need to try and force it to be more specific here on the type level to actually infer what we pass into here and then use that to index into the object that we have on the type level as well for this we're going to need multiple generics so we have our T OBS here we can actually add a second argument here where we can say t obj and t key and this key now we can put it on here but we now actually get an error because T key cannot be used to index type T object and we've lost all our nice autocomplete and we've lost our nice return type too so we need to think we actually need to constrain T key but still infer it for that we can use a constraint so we can say t key extends key of T obj Here and Now what we get is we get this inference but it actually Returns the proper type whatever we pass in so if we specify a we get number if we specify B we get string if we specify C we get Boolean this is extremely cool because you can get this inference happening between the arguments of your functions which then maps on to the return type and lets you sort of propagate these types beautifully through your app generics are so cool tip number nine let's go back to basics a little bit we have a function called create set where we're taking in a dynamic T but this isn't referenced in our type arguments anywhere so it's not going to be like inferred from anything this means that in order to get something sensible back from it we basically have to call create set passing that number into there which then gets passed to our set there so we end up with set number but we have this create set down here which is unknown because we haven't actually passed it anything inside this slot but I've got a requirement which is I would actually like this to be a set string and we can do that by actually specifying it as a default parameter so just like you can have default parameters in JavaScript you can have default type parameters in typescript so here this T if we don't pass anything is going to default to a string which we can see down here because set is now inferred as string but if we do pass something to it then it overrides it and puts it in its place tip number 10 one of the most powerful things you can do in typescript is use generics to link up amazing inference from external libraries in this function we we've got a function called make Zod say fetch which will give you a clue as to where we're going we have a t data parameter here which we're using to pass a type argument to and then we're getting back this result here first name and last name but you notice this is an actually safe at runtime we're just sort of saying that this is what's going to get returned wouldn't it be great if we could use a schema definition library to make sure what's coming back is what we think is coming back and have that automatically inferred wouldn't it be great wouldn't it be great for this we're actually going to use Zod we're going to import from Zord and zodges has a single import Z then we're going to pass a schema into our function here and for now this is just going to be Z dot schema so now this is forcing us to add Z dot whatever into here and let's say we just want first name which is z dot string and then last name which is z dot string and inside the function we're then going to say then the result that comes back from res dot Json we're then going to return schema dot pass that result so the runtime what this is going to do is fetch for my URL then call res dot Json on it then take that past result and actually run it through our schema parser whatever we pass in here so we know that what we're getting back is going to be first name and last name but the issue is we've still got this weird kind of like doubling up here where we've got the type argument there and then we've got this Z dot object being passed in there wouldn't it be great if one could infer from the other well it turns out this Z dot schema actually has a couple of type arguments that you can pass to it and the one that we care about is T data there this means that if I add something to this for instance then this is going to start erroring because I need to add idz dot string onto here and what it also means crucially is that I can delete this type argument and result will still be inferred as the right thing and not only is this safe on the type level it's also safe on the runtime level too and we've managed to make this really nice type save function without any type parameters actually here so you can see that if you concentrate your types your advanced typescript in one place it means that the users of your function don't really need to worry about it at all there we go folks typescript generics like what a mind-blowing feature of this amazing language you can learn more about generics by leaving a like on this channel that's right pressing that like button will actually inject your brain with a kind of surge of dopamine which you know will inject some generics in there somehow just click the like button and you check out total typescript.com which is my course for working with Advanced typescript it's got an entire module which is about the length of a five hour Workshop entirely dedicated to generics thank you so much for following along you can actually look at one of my other videos here you can subscribe see what more stuff we've got on the channel and I can't wait to show you even more typescript stuff I'm going to be focusing on the real core stuff to do with typescripts to make you a better typescript developer and to lead you through the forest of advanced typescript that's out there thank you very much and I'll see you very soon
Info
Channel: Matt Pocock
Views: 140,619
Rating: undefined out of 5
Keywords: typescript, web development, advanced typescript
Id: dLPgQRbVquo
Channel Id: undefined
Length: 18min 19sec (1099 seconds)
Published: Thu Jan 12 2023
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.