Loading video player...
There are 18 different utility types in
Typescript and each one has its own
specific use case. And if you don't
understand how to use these properly,
you will never advance past a junior
TypeScript developer. So, in this video,
I'm not only going to show you how to
use every single one of these different
TypeScript utility types, but I'm also
going to show you when you would want to
use them and why they're useful. Also,
if you want to follow along with this
video, I have a full cheat sheet that's
in light mode and dark mode that you can
download. I'll put in the description
down below a link to it. You can
download this full cheat sheet and
follow along exactly with all these
different code examples and so on.
Welcome back to WebDev Simplified. My
name is Kyle and my job is to simplify
the web for you so you can start
building your dream project sooner. And
to get started, I want to talk about
some of the object related utility types
because there are some of the most
important ones you're going to run into.
In this simple example, I have a user
object here that's a type that has an
ID, a name, an age, and an address
object on it. And I have some various
different functions. I have a create
user function, an update user function,
and a render user details function. And
each of these takes in a user object,
which seems relatively self-explanatory
until we start looking a little bit
further into how the code works. For
example, when I create a user, I don't
actually want to pass the ID of the user
in because I want that to be generated
by my database or some other
methodology. So, I want to pass in
everything except for my ID property. My
update user, again, I don't want to pass
in all these properties. Maybe I only
want to update the name or I only want
to update the age. I want to make it so
that some of these parameters are
optional. And then we can get even
further to render user details. This
only uses the name and the age and
nothing else. But I'm still requiring a
full user object to be passed in. So if
I try to call render user details and I
pass it a name of Kyle and I pass it an
age of 30, you can see that it's still
throwing me an error because I need to
pass in all these other properties of
address and ID. So we can actually use
utility types to make this work much
better. For example, render user
details. We can use the pick utility
type. This utility type is a generic
that takes two different properties. The
first is the object that we want to be
able to pick properties from. So we pass
it in our user. And the second is all
the different keys we want to get. This
is a union of all the keys we want. So
in our case, we want the name and the
age properties. So we can put those in
as a union by putting this pipe symbol
between them. And this code essentially
says take my user object and pick just
the name and age property from it. So
now this user object only has a name and
age property. And if we were to come in
here and we now call render user
details, you can see I can only pass in
the name and the age property just like
this. And once I pass both of those in,
you can see it works just fine. And if I
had a full user object, so we'll just
say this user object right here has a
name of Kyle,
age of 30. We come in here with an
address,
city, we'll just put in some random
information. Same thing here for street.
Doesn't really matter what it is. And
then finally an ID. So we have all this
user information. I can still pass this
user on just like I would before. But
now if I want to pass on something
that's not a complete user, I can do
that just fine. And the reason you would
want to use this pick property instead
of just coming in here and saying name
string age number cuz that would also
work as well. The reason we want to use
that pick property though is what
happens if in the future I change my
name property or my age property and now
my name is for example something else or
my age I change it to a string instead
of a number. I wouldn't actually do
that. But if I were to change that
particular thing about my code now, you
can see it updates properly down here
automatically because we're using a pick
property here. So we don't have to worry
about manually changing that in every
place. Everything is tied back to this
main type. And that's kind of the whole
idea behind these utility types in
Typescript is it allows you to have one
base type that everything's tied to and
we can derive new types from that and
then we can update this one base type
and make sure everything else updates
accordingly. This is very similar to
clean coding practices that you'll see
inside of various projects for like
JavaScript, but it works specifically
for our TypeScript types. Now, for our
create user function, I mentioned how we
don't want to pass the ID in, but we
want to pass every other property in.
Well, again, I could manually define
every single individual property. But
now, if I change my user, I need to also
change it here, which again leads to
different dependencies I don't want to
worry about. It's bad code. So, instead,
we can use a little helper type called
omit. And omit is great when you want to
remove specific properties. It's the
exact opposite of pick. Essentially, I
pass it in the type I want. And then the
second parameter I pass is all the keys
I want to remove from it. So I can say
ID right here. And now this new user
type is essentially all the properties
from my user here minus the ID. So if I
were to call create user, and I look at
all the keys I have available, you can
see I have name, I have age, and I have
address, which if we type that in as our
actual address information.
Just like that. You can now see that
this code is working fine because we
don't need to worry about passing that
ID property in because we said
essentially give me every user property
except for the ID. And now if I change
this by removing my age from here, you
can see down here I can remove the age
and now everything works just like
before. They're all derived from one
another, which makes my code much easier
to work with. So pick and emit are
probably the two most common different
types that I use for the utility types
because they allow me to really easily
derive one type from another. One of the
next most common that I use is the
partial type. We talked about update
user. I want to pass in essentially any
of the properties I want to update. Do I
want to update the name, the age,
address? I don't know which one I want
to update. I just want to update one of
them or maybe multiple of them. So
instead of passing a full user object, I
can instead parse a partial. So this
partial type takes a single type as the
generic parameter. And all it does is it
makes all the keys in that type entirely
optional. So now all the keys for my
user are optional. So if I call update
user, you can see address, age, ID, and
name are all optional. I can pass as
many or as few as I want. So, for
example, I could change the name to
Sally and everything's working fine. Or
I could change the age here and say the
age is 20. And again, everything's
working fine. I can pass as many or as
few properties as I want to this option
and it'll just update them based on that
information. So, partial is really
useful for when you want to have like
update syntax that's going to take a
type that you normally have required
properties for and just make every
single one optional. So, I can update
only specific things inside of it. Now,
you may also run into a scenario where
maybe you have a type where certain
things are optional. For example, in our
case, the address is optional. Let's say
that that's a change we make and we now
have a function that just says create
user with address. And this is going to
take in a user just like before. But in
our case, we want to make sure that the
address property for this user is
required. Currently, it's an optional
property on our user. So we can use the
opposite of partial, which is required.
And this required type essentially does
the exact opposite. It takes every
single property that is normally
optional and it forces you to pass that
along. So now if we call create user
with address, you can see that this
address property is required. While if I
remove that required type and we look
back down here, you can see this address
property has a question mark next to it
because it's now no longer required. Now
another really common utility type
you're going to run into is when you
want to deal with immutability. A lot of
times, especially when you're dealing
with functional programming,
immutability is really important inside
of JavaScript. There's actually a
function object.
If I can actually spell properly. There
we go.
This object.freeze just takes whatever
object you pass into it and it
essentially freezes that. So you can't
modify anything inside of that
particular object. You can't change any
of the keys or anything like that. You
can actually represent that in
TypeScript by using the readon property.
So for example, I can come in here and I
can say read only. And now this ID
property is read only. So if I were to
have a user object of that user type and
I were to say like user ID is equal to
something, I'm going to get an error
because essentially ID is a readonly
property. So I can't redefine whatever
that particular property is. Now in our
case, what if we want to change
something to be readon because right now
our user is not readon. But what if I
want to return a immutable readonly
version and I want to make sure
TypeScript enforces that. Well, I can
use the readonly type. So I can say read
only just like this. It's a type just
like that. I can pass in my user and I
want to make sure that I spell this
properly. It's lowercase readon. Just
like that. I can say type T is equal to
readon. And now this T type is
essentially a readon version. You can
see it just prepends readonly to the
beginning of every single property. You
will notice that it doesn't actually
nest this. It's only one level of
readon. So, it doesn't do deep levels of
nesting. So, I could still change my
address, street, or city. But, I'm
unable to change my ID, my name, age, or
reassign my address to a specific
property. So, this allows me to just
mark an entire object as read only,
which is really useful. Now, one of the
most misunderstood, but still incredibly
useful properties is the record type
inside of TypeScript. Let's just come in
here. We'll create a type called T. Set
it equal to that record. And this again
takes two different parameters. The
first one are the keys that we want to
assign and the second one is going to be
the value that we want to assign to
those keys. So oftent times you may see
this with something like string at the
start saying that we want to have a
record which in our case is an object
where all of the keys are just some type
of string and the object here could be
like our user type for example. So now
we essentially have an object where all
of the keys are strings and the return
values is a user. So if we just say
const a is of that type t. Now whenever
we set a string key it doesn't matter
what it is is we have a key here that's
a string we must give it a value that is
a user you can see age ID name address
and so on we have all those different
properties but you can actually take
this a step further and this is where I
find this to be incredibly useful and
instead of just having a generic string
here we can use a union for example I
can have for example admin or user this
is my union type so now I have a record
which always has an admin key and always
has a user key that are both set to a
user object and we can set this to
something else. We could just say like
test which is a string. There we go. So
now if I were to create an object of
that type and I set it equal to a value,
you'll notice that I get autocomplete
for the admin property which I must set
to an object that has a test of string.
And we get a user that must be set to a
test of a string. And that gets rid of
all of my errors. But if I, for example,
don't add the user, you'll notice I get
an error because it's supposed to have
that user type even though it's not
there. So this is a great way for me to
create objects that are forced to have
specific keys with specific values. Or
if you want to have an object that can
have general keys, you can just come in
here with string. Or in our case, you
can use the property key, which is like
a fancy TypeScript type to essentially
say anything that's a valid key inside
of JavaScript. So this allows you to
create essentially a type for an object
that can have any key at all, but it
always has a value that is a test of
string. Now, since we're kind of on the
track of these different unions where I
was talking about admin and user here,
this is a union type. There are actually
types that we can use for this inside of
TypeScript. So, let's come in here and
I'll say type ro
is equal to and we'll have admin or user
or moderator.
So, now we have this role type that can
be any of those three different roles.
And that's great, but sometimes I want
to get subsets of these or only specific
properties from these. For example, what
if I only want to get the roles of admin
and moderator? What I can do is I can
just create a new type here. And I can
use the extract custom property here,
this type. And this allows me to pass in
a union. That's what this T is. So we'll
say roll here. And then it allows me to
pass in another union of the things I
want to get from it. So I want to get
only the things that are going to be
admin and moderator. Just like that. So
now if I look at my type of T, it just
extracted out the things from this type
that are also inside of this type. And
the really great thing is is it allows
me to essentially combine together
multiple different types into one. So
let's say that I want to create another
type here which is just going to be
other roles. And let's say this other
roles has some extra roles like testing.
It also has an admin role and it's going
to have a user role. And let's just say
it has another role something like
security. It doesn't really matter. So
now we have these two different types of
unions. And I want to get what the
intersection of those are. Well I can
pass one union to the other one. And
essentially what this is saying is give
me all the properties in RO that are
also in other role. So if I hover over T
we get admin and user because those are
the two things that are shared between
these two different types. So extract
allows me to essentially create a union
of my unions by only getting the things
that are shared between the two of them.
That's kind of the main use case for
extract. Now exclude which is
essentially the opposite of this is much
more useful for various different cases.
So we can come in here with exclude and
let's just get rid of our other role.
And now what I'm doing is I say I want
to get all of the roles here. I want to
get just specific properties from them
and exclude the others. So if I want to
exclude the user type, now my type T
here is either admin or moderator. So
this is really great for just like
narrowing down a type to be more or less
specific. In our case, we're removing
that user type. So we only get the like
super powerful admin and moderator
users. We can also use this with our
other role type. And again, what it's
going to do is it's going to take my
role type and remove everything that's
shared between this one and this one. So
now if I hover over this, you can see T
is just a type of moderator because our
other role has admin and user which are
being removed from this top one. So
essentially it takes this first type
here of union and removes everything
that's inside of this second one. More
often than not though, I use this in the
other example where we had where we just
pass individual types or a union of
types to remove the ones that we no
longer want from that particular union.
So here you can see we just get admin
because we removed user and moderator. I
use this all the time when I'm dealing
with various different enum related
things and I need to make sure my code
is slightly different for when I have
more enums or less enums being
available. These are perfect
opportunities for that. Now, two types
that are going to give you absolute
superpowers that you may not think about
is the return type and the parameters
type. Especially parameters, it's
incredibly useful for specific things.
So, let's come in here and just say I
have a function called get user which
has an ID of a string. And then it's
just going to return to me a type which
will just say name is Kyle
and the ID is the ID. Whatever it's
supposed to do some database queries or
whatever. It doesn't really matter. We
just have this get user function and I
want to get the type information from
it. For example, I want to get what is
the type of the thing that this returns.
Well, I know it's a name with a string
and ID that's a string. But I don't want
to manually type that out. I want this
to be automatically inferred for me.
Well, this is where the return type
function comes in from Typescript. So,
we can say type T is equal to return
type. This takes in a single argument
which is a function. We can say get
user. But specifically since this is a
function defined in our code, we need to
get the type of that function to get the
TypeScript version. And now if we hover
over T, you can see the type is a name
which is a string and an ID which is a
string because that's what this function
returns. If we gave it another property,
for example, an age, and we hover over
this, you can now see that's
automatically inferred for us. So
TypeScript just figures out what this
thing returns for us and gives us the
type for that, which is really useful.
We can also do a very similar thing with
parameters. So parameters allows us to
get what the parameters are and it
returns to it a tupil that contains them
all. So let's say that this took
actually multiple different parameters
like this. Takes a string which is an ID
and an age which is a number. If I hover
over my type T, you can see it gives me
an array where the first property is a
string and the second property is a
number. You can kind of ignore these
names on here. They don't really matter.
Really the important thing is it's a
string and a number tupole. So if I
wanted to assign something, for example,
a variable A of that type T, it would
have to have a string as the first
property and it would have to have a
number as the second property.
Otherwise, it would not work and throw
me an error. If this was a string, I'm
going to get an error inside my code. Or
if this was, for example, a number,
again, I'm going to get an error because
it must be a string followed by a
number. And if I try to add additional
properties onto here, I'm again going to
get an error because it's only supposed
to have two specific properties. So when
exactly would you want to use these
particular functions? The main one for
return type is anytime you're using a
library that has a function inside of
it, but it doesn't give you the type of
what that thing returns, but you want to
use that type in your code, you can wrap
it in a return type, and now you can
actually get the access to the return
type of that function. Again, it's great
when libraries expose to you functions,
but they don't expose the type of what
that thing returns. Now, parameter is a
little bit more of a niche use case, but
it's incredibly powerful when you want
it. Let's say that we have a function.
We'll just call this function. get user
takes in an ID which is a string
and again it's just going to return us
our user object just like that whatever
it doesn't really matter and then we
have another function we'll call it get
user wrapper
and this get user wrapper is going to
take in some additional stuff for
example it's going to take in an ID
which is a string and then it's going to
take in some other property we'll call
it a boolean it doesn't really matter
and inside of this function we're going
to be calling get user and pass it along
that ID property and let's just say we
return that and we use this other
property for something else. It doesn't
really matter. This is kind of a
contrived example. But you can see that
these two properties for ID are linked
to each other. If I change this to a
number, all of a sudden my code's going
to fail. And I need to make sure I
update this here as well. So I have
these linked properties. And anytime you
have things that are linked in
Typescript, that is generally a sign you
should probably be using one of these
utility types. So in our case, we can
actually say that this is going to be a
parameter. So we can get the parameters
from the type of get user and we can get
the very first parameter from here
because that's the one we care about.
And now these are linked together. If I
change this to number, it automatically
works because this changes to a number
right here automatically because we're
getting that directly from the
parameter. So whenever you're wrapping
one function in another function,
parameters is often times very useful
because you're generally passing along
these parameters to that wrapped
function. So, making sure you extract
what those types are. Make sure that
even when that internal function
changes, everything else around it
updates automatically for you. Now, if
you're used to more class-based
programming, you may have code like this
where you have a user and then that user
is going to have a constructor. Let's
just say that it takes in a name. This
name is equal to name and we'll say the
name is a string. Sure, something like
that. We can say name
is a string. There we go. So, there's
our really simple constructor code. It
just takes in a single parameter and it
returns to us this user object. But what
if you want to get the parameters of
what that constructor function takes?
Well, you can't use the parameters
function that we've already been using
because that only works on normal
functions. It doesn't work on
constructors. Instead, we come in here
with a type of T. We need to use the
constructor
parameters function, which is
essentially the exact same thing, but it
works on constructors. And we pass it
our class. So, in our case, we have our
user class right here that we can pass
into this. We just need to make sure we
pass along the type of what this class
is. And now if we hover over our type T,
you can see we get that tupil which has
a single value which is a string. So
this is essentially the version we use
if we want to deal with a class. Now
technically we could do the exact same
thing to get the return type of a class
by using the instance type and passing
in the type of our thing. But this has
no use case for classes cuz this just
returns to us the user type. It's the
exact same thing as if we just wrote
type T equals user. They are exactly the
same. So there's really no point in
doing this for classes. Now the next two
types I want to talk about are really
useful utility types. The first one is
just removing null from something. So if
we have a type here which is just a and
that is going to be equal to a string or
null. And I want to make it so that this
thing can't be null. I can just come in
here. We'll make our new type. We can
use nonnullable and pass it in our type.
So this just takes one single type. And
all it does is remove null and
undefined. So if I hover over t, you can
see it's just a string. I could add
undefined to this type as well. And it
still removes that. It essentially
prevents something from being null or
undefined. Now, you may think this has a
lot of overlap with the required
property, but there's a couple
differences between how these things
work. First of all, required is meant
more for objects than it is for
individual properties. And secondly,
required actually doesn't remove the
ability for something to be null or
undefined. It just forces it to be part
of the object. For example, if I come
into here and I say that this a type is
going to have a name, which is going to
be an optional string like this, and I
say required, just like that, and I
hover over T, you can see it gets rid of
the ability for it to be optional. But
if I also say that this could be null
like this and I hover over T, you can
see it doesn't remove the ability for
that thing to be null, it just makes it
so that is no longer an optional
property for my particular element.
While non-nullable removes that actual
null attribute from it. So if you have a
type and you want to remove the ability
for it to be null or undefined, you want
to use non-nullable. Now the next thing
I want to talk about is when you have a
promise. This is really really common
code. Let's say that I have a function
called get user. And this is going to be
an asynchronous function because it's
going to be accessing my database. And
here we return whatever my user is. But
this is going to be a promise. So we'll
say promise.resolve just like that. And
it's going to return to me a brand new
user. Obviously this would be accessed
in a database or whatever else. But
oftent times when I get the return type
of this. So I can say type t equals
return type of my get user function. If
we look at this, you can see it's a
promise that returns to me an object
with a string inside of it. And
generally I don't care about the promise
aspect. I want to get what is inside my
promise. This is where the awaited type
inside of Typescript comes in. The
awaited type just takes a single type
passed into it and all it does is it
gets rid of all the promise stuff and
just tells you what the promise returns.
So in our case when I hover this, you
can see now instead of returning a
promise, it just gives me what that
promise itself would return. And the
nice thing is it doesn't matter how many
levels of nesting you have inside your
code. For example, if you have 10
different levels of promises all nested
inside of each other, it'll strap out
all of that and just give you whatever
is left after all the promises finish
resolving. The most common way that I
use this is just like this where I have
a function that I want to get the return
type of. But it's an asynchronous
function. So, I need to make sure I
await that before I get what the actual
return type is cuz more times than not,
I care about the actual return type and
not the promise itself. Now, the last
four types I want to talk about, we can
rapid fire through because they're quite
simple and they are all string
manipulation types. For example, if I
come in here, we're just have a type s
that's equal to hello world. Just like
that. And now I can modify what this
string type is looking like. So I can
just get my new type T and I could, for
example, convert it all to lowercase
just like this. And now if I hover over
my type T, you can see the entire thing
is lowercase. I can use uppercase
if I can spell properly. And if I hover
over T, you can see it converted the
entire thing to uppercase. I can also
use uncalize.
Unc capitalize just takes the first
letter and makes it lowercase. So you
can see here the first letter has been
converted to lowercase. And I can also
use capitalize, which is the exact same
thing, but it's going to take this first
letter and it's going to capitalize it.
Now, you may think, what in the world is
the point of these? I know when I first
saw these different string types, I
thought they're entirely useless. But
these are incredibly useful as you start
to write really complicated TypeScript
code that's more behind the scenes for
libraries, such as converting things
that are in Pascal case into camel case.
So going from this camel case into a
Pascal case, this is a perfect use case
for that. And don't forget, if you want
to get this full TypeScript utility
types cheat sheet that goes over all 18
of these types with example code, as
well as descriptions of when you want to
use them, why you want to use them, and
what makes them important, it's going to
be linked down in the description below
for you. It's entirely free, so I highly
recommend you check it out. With that
said, thank you very much for watching
and have a good
FREE TypeScript Utility Types Cheat Sheet: https://webdevsimplified.com/ts-utility-types-cheat-sheet.html Learning the 18 most important TypeScript utility types is one of the quickest ways to go from a junior to a senior level TypeScript developer. In this video I will cover the 18 TS util types that I use every day and that you need to know in order to truly master TypeScript. 📚 Materials/References: FREE TypeScript Utility Types Cheat Sheet: https://webdevsimplified.com/ts-utility-types-cheat-sheet.html 🌎 Find Me Here: My Blog: https://blog.webdevsimplified.com My Courses: https://courses.webdevsimplified.com Patreon: https://www.patreon.com/WebDevSimplified Twitter: https://twitter.com/DevSimplified Discord: https://discord.gg/7StTjnR GitHub: https://github.com/WebDevSimplified CodePen: https://codepen.io/WebDevSimplified ⏱️ Timestamps: 00:00 - Introduction 00:40 - Why use utility types 01:48 - Pick 03:48 - Omit 04:59 - Partial 05:54 - Required 06:38 - Readonly 08:10 - Record 10:05 - Extract 11:45 - Exclude 12:53 - ReturnType 14:03 - Parameters 16:49 - ConstructorParameters 17:48 - InstanceType 18:06 - NonNullable 19:24 - Awaited 20:45 - String Manipulation Types #TypeScript #WDS #TS