- Hey Matt, thank you so much
for taking your time to talk with me.
Do you wanna just real quick introduce yourself,
kind of talk about what you do, your experience
with TypeScript, that kind of stuff?
- Sounds good, Kyle.
My name's Matt Pocock.
I am a TypeScript educator, I guess, full time.
I used to work at Vercel as a DevRel
and then at Stately on the XState core team as well.
So I've been a TypeScript developer for a long time
and I have a course called TotalTypeScript.com,
which is where I sort of do a lot
of advanced TypeScript stuff.
So I think that's what we're here to talk about today,
right, the kind of more advanced TypeScript end of things.
- Yeah, definitely, and like, Matt's really
underselling himself, like when I think of TypeScript,
I think Microsoft and then I think Matt Pocock,
like in that order, maybe even reverse.
Like, he is like the TypeScript guy
when it comes to advanced TypeScript.
So that's why I really wanted to talk with him
because his advice, I think, is some of the best out there
when it comes to TypeScript, and like he said,
this video is really all about talking about kind of
how you transitioned from maybe an application developer
where you're writing TypeScript code to maybe moreso of
an advanced TypeScript developer where you're kind of
writing the behind the scenes code for your company
or even a step further where you're like, moving into
more of like the library space where you have
to really know a lot of niche TypeScript stuff.
So kinda the first question I maybe have for you Matt,
is what is the first step someone takes if they think
they want to start to learn more about TypeScript
and really dive deeper into it,
where's kind the first place they should look
or the first step they should take to doing that?
- Yeah, from how you've described your course,
it sounds like you're getting people to where you want.
Basically you're feeling competent as
an application TypeScript developer where you're saying,
okay, I want to like, I'm coding in React or something.
I'm working with all the stuff the framework gives me.
I'm making sure that all my types are correct
and I know how to like avoid using any,
avoid using kind of the more unsafe stuff in TypeScript
and you're at that point, but you might still be
at the point where you occasionally come across an error
and you think, I have no idea what's causing this.
I have no idea how to do this, or you might think
of some JavaScript and you might think,
I just have no idea how to express this in TypeScript.
Like, the types seem just way too complicated for me.
Let's say you're building like, a generic fetching hook
or something like that and you're going,
I just don't really know how I would even approach this.
It's at that point that you start thinking about,
okay, what is there left for me to learn in TypeScript?
And when you start thinking about that,
you're really going from like a developer
into someone who can build more sort of generic stuff
throughout the application and throughout the company
because as your code base grows, you're gonna start to need
to share code more across your entire application,
and as that grows and grows and grows
and you get more shared code,
that shared code is probably needing to be quite generic,
needing to be quite complicated because it needs
to handle lots and lots of different situations,
and so you kind of go from the, from like an app developer
into like a utils folder developer, you know, which is,
I mean I don't know if you've experienced that as well.
- Yeah, I kind of find that when it comes to writing
TypeScript code, if you have to deal with like one generic,
most people that are kind of in that application stage,
that's not too bad, but once you start to deal
with multiple generics in the same function
or you have like, a really complex generic
that has a lot of like, default types and extends
a lot of stuff, that's when I find a lot of people
kind of get to the point where they're like,
ooh, this is really scary stuff.
I don't really know what I'm doing,
and that's kind of that next step that you're talking about.
- Exactly, or you start doing things with like,
even 'cause we talk a lot about like generic functions
or things like that, but there's a lot of complexity
just in like generic types as well, you know,
like a type that can receive another type
that goes into another type and it's sort of all of
this plumbing that you have to do in order just to make
your types sort of reusable and interesting
and kind of mapped to your like, domain logic.
So if you've got like an API response that you know
your API is always gonna come back with this sort of
same shape, either like an error property
or like a data property, you probably wanna put that
inside its own type helper or generic type so that
you can then reuse that across your application,
and it's knowing that sort of thing that gets you into,
I mean it's an interesting one
'cause we can talk about like, the benefits
or I guess we can talk about the benefits
of knowing advanced TypeScript later,
but like how you get there is you often need to just
sort of like plow through a bunch of documentation
and just try things out and usually, you know,
scream on Twitter, which is mostly
(Kyle laughing) how I sort of got my start
and like and how I learned this stuff,
'cause I was doing a lot of like library development
as well, so when I was working on XState,
which is like the third level, so when you've kind of
got to the point where you can go,
okay, I can build some interesting utilities,
I can kind of, I know how to make shared code
that works across my application across my organization.
From there you go, okay, let's start maybe
open sourcing a library or maybe making a library
that's used across my company or things like that,
and that's, I mean that's where
the real complex TypeScript is.
I don't know, like, I mean you've probably seen
like, applications, like libraries out in the wild
that do that stuff.
- Yeah, yeah.
I almost view TypeScript in like a library form as like
a whole separate language 'cause there's so many new things
you need to know and there's so many things you have
to keep in mind like when you're doing that
because when you're writing TypeScript code for yourself,
you don't have to think super wide and super broad,
but when you're writing TypeScript code in a library,
you have to think, how is the user and developer experience
of this code going to work?
And that's kind of what you're writing your types around,
which is a much different experience than,
I just wanna make sure that this is type safe,
if that makes sense.
- Yeah, and it kind of comes down to like how much types,
how much type level stuff you are writing
versus how much runtime level stuff you're writing.
So when you are in application mode,
often you are like maybe there's like 10 times
more runtime code than there is type code.
You know, you like, you annotate the parameters
of a function and then bam you write a bunch
of JavaScript essentially and then you don't have
to worry about anything else, and it just works.
When you get into the utils folder,
it's sort of like one for one, right?
You might have like, a group by function
that needs like a generic parameter
that does a little bit of type transformation in there
and so your functions start getting a bit fatter
and start sort of having a bit more TypeScript annotations
onto them, and then when you get into library code,
like it's completely the other way where I saw the main,
one of the maintains of Redux called Lenz,
Lenz Weber-Tronic, who's an amazing TypeScript developer
and really revolutionized the whole
TypeScript thing in Redux.
He basically said like, there's probably about five times
more type code in Redux than there is runtime code
'cause the runtime is very simple and,
but the type code is extremely complicated 'cause of all
the different things they need to handle, absolutely.
- Yeah, I've noticed when I've looked at open source code
for like, really good TypeScript libraries,
the first couple hundred lines are just type definition,
type definition, type definition, and then you get
to the actual code and it's like a couple hundred lines,
but you have hundreds of lines of just type definitions
before you even get to that point,
and that's not even mentioning the type definitions
in other files that are importing in.
It's just absurd how much TypeScript you need to write,
which is why I actually like application development
because the amount of TypeScript you need to write
to get the benefits of TypeScript is so small
compared to when you're writing out a library.
You have to know so many more things
and you're really a TypeScript developer at that point
'cause you're not even writing much JavaScript
compared to TypeScript.
- Absolutely, and I think a really interesting point there
is that there, in libraries, the reason you write that much
is so that when people consume your stuff they don't need
to write type annotations, right?
If you're using React Query, let's say,
which is a great example, it's got like, I don't know,
last count I think there were like nine function overloads,
so like different definitions of how you can call
the useQueryhook, 'cause they've got
all sorts of different APIs.
I don't know if you cover a useQuery in like,
I imagine you have in oodles of your videos
and your rec course and all sorts of stuff,
but when you get to that point and you are
actually using it, you don't really need to like,
use any types at all.
You just sort of declare the types on your own function,
pass those to React Query,
React Query understands everything,
even if you're using like a select function
or things like that and bam, you're at the races.
So I mean, it's capturing that type behavior in libraries
is the whole point, 'cause if we didn't have
these really powerful tools like generics or like,
function overloads or conditional types,
then we would just need to do a lot of duplication.
We would need to say okay, we're passing something
to useQuery, but useQuery has no idea what it's being passed
so we need to actually annotate the return value
of what we get back.
So we need to say this data is this
and we wouldn't get any type safety there.
So I don't know, it's just magic working with Typescripts
when it's that powerful, you know,
and you can build these beautiful abstractions
that just make other developers faster
and make the language feel more powerful.
- Yeah, definitely.
Well, let's kind of go back to like
the utils folder kind of developer.
Like, let's say that you are working at a TypeScript,
or company, you're writing TypeScript code,
you're on the application side and you want to maybe move
into a more advanced TypeScript role.
You wanna start writing those util kind of function things.
What would you recommend are the best steps for a person
that kind of wants to move more into TypeScript
to position themselves and to learn the things
that they need to actually get more into
that TypeScript focused role?
- Yeah, I think the first thing you need to know
is you need to know what's possible, right?
And in TypeScript really like if you're,
it's tricky because you not only need to know like,
advanced or relatively advanced TypeScript in order
to do that stuff, you also need to think about
the design of the APIs that you are creating.
So if you're building like a set of functions to sort
of handle sort of, I don't know, like transforming some data
for instance, then you need to know,
okay, what do I want like these all to be on one object?
Do I want lots of different functions
that all take the same thing?
Do I wanna make it a class or things like that?
And so taking a look at sort of the design
of open source libraries and getting a feel
for what's out there and what's good is I think
a really, really key thing to do.
So take a look at that first, see what's popular out there
and see maybe if you can mock up like a version
of React Query that just does a sort of like, a simple thing
that you want to do, like infer the type,
let's say you pass it a function that returns
a promise of something, you want it then to just come back
with like the data or something
and just mock up a little bit of what React Query does,
and then from there you can start thinking,
okay I know how to do that and now I can start actually.
Let's say you notice something.
I remember when I was working on a team where,
this was actually the team where I first
discovered TypeScript and we had this like,
really annoying issue where, well not an issue actually,
just like a practice where every time we needed to build,
like a new table, it was a React app, we were working with
a ton of crud data, every time we needed to build
a new table, we would build a new table component, right?
And so it would have like, I don't know,
like it was something to do with cars I think,
or product table or car table or car make table
or something, and so we just had like 20 components
that all basically had the same structure,
basically had the same CSS and it was like okay,
I could definitely make this more dry.
Definitely, definitely, and, but what I would need
is I would need to be able to pass some data to the table
and then think, okay, 'cause I looked at React table
and I thought ooh, okay.
They use like a renderRow function, that's interesting.
So if I pass in the data, I need that row that's passed
to the renderRow function to be the same as the data.
If it's a product, that needs to be a product.
If it's a car, that needs to be a car,
and so I thought, I can't do this in JavaScript,
and so that's actually when I reached for TypeScript
'cause I knew that TypeScript could do this
'cause I'd seen it in like a conference talk or something,
and I kind of changed the whole web cap.
I probably shouldn't do this, but I had a very leading boss
who let me do this, thank you Will if you're watching,
and he basically said, yeah, just give it a go.
So I changed our web pack config.
Meant that we could add TypeScript to the code base
and my first ever TypeScript component that I wrote
was a generic component.
- Way to go in the deep end.
- Oh yeah, absolutely,
(Matt laughing)
and I loved it and and it, and it meant I could just destroy
a bunch of code because it, we now had a way to capture
this reusable component in a way that made sense
and it was brilliant and actually like,
significantly sped up our velocity because we now had a way
to just like quickly bash out a bunch of pages,
and I kept on sort of doing that, kept on finding
these abstractions and it, we went from like being
really far behind the back-end team
and having loads of front-end work to do
to being clean ahead of them actually.
It was massive.
So I can't remember what your original question was,
but like, if you can find opportunity.
Yeah, I remember what it was.
It was about being able to,
how do you actually make that leap?
How do you, like.
I would say yeah, investigate APIs,
investigate the open source land and seeing what's possible
and finding the sort of building blocks,
I think is quite tricky as well because like,
I mean, I'm interested in your take as well.
How did you actually like get into working with generics
and like, what's your feeling about the best way
for people to get into it?
- So for me, I had a little bit of advantage
'cause my programming history was in typed languages.
Like, I was working in like C and Java
before I moved into JavaScript.
So I'd already seen generics before and then when
I found out about TypeScript I was like,
oh I kind of already know what generics are,
so it wasn't too bad for me.
I find that if you like, look at code that already exists,
kind of like you said, that's a really good way
to start to see examples, 'cause when I started to do
more advanced TypeScript, 'cause Advanced TypeScript
is a lot different than other type languages
'cause there's so many really cool things you could do.
It's almost like its own programming language
for writing types, so when I started to dive into
that kind of stuff, the best thing that helped me
was just lots of Google searching, blog articles
and just looking at code examples
because I find that the TypeScript documentation,
it's really good at some things
but I find it really hard to search through.
Like, when I was researching for this course
I read a lot of the documentation and what I was reading
was really good, but when I wanted to find specific things
like satisfies for example, the only place I could find that
was in the release notes for the actual release
that came out, and so it's like very difficult
to find specific things there.
So I think looking through open source core code,
finding different like Stack overflow examples,
things like that I find were more helpful for me
when I was learning that advanced TypeScript
than the actual documentation.
Once I kind of found it in the documentation,
obviously it was great but finding it
was sometimes the hardest part.
- It's unreal actually.
Yeah, that's something that I've raised
with the TypeScript team actually is like,
I think I put a poll on Twitter saying like,
if you put something in the release notes,
does that count as it being in the documentation?
And I think Ryan from the TypeScript team said,
I feel personally called out on this,
because. - Yes.
(Matt, Kyle laughing)
- They say yes, it is.
I say no, absolutely isn't.
Like, it's so there are tons of stuff actually in there
like as const, assertion functions, type predicates,
a bunch of ts-config changes that are only
in the release notes, and so if you get serious about it,
I would say yeah, read through the handbook
and then read through the most recent release notes,
'cause there's a ton of stuff that's in there
that you just don't realize.
I think too, like one thing that's really cool
about TypeScript is that they, they make a lot of changes
actually to the way that the types work and over time
they add more features like satisfies and they came out
in 4.9, I think relatively recently.
The whole sort of like template literal thing
where you can do sort of like string interpolation,
string manipulation in types only came out in 4.1,
like I guess that's a few years ago now,
but like when they add certain features,
like they added the ability to export types only,
well because of that then that actually gave birth to TRPC,
which is now one of the most popular routers out there
and like backend frameworks because it meant that
you could have a route, some router code, server code
and then say just export this type of this
and then you could import that with, into your client code
without needing to do any kind of other manipulation.
So it meant that that whole flow was possible
of like inferring from the backend which just
wasn't possible before, and so just let
your imagination run wild.
When you see a feature and you think,
can this really enable that, that's incredible.
It probably can and if you get used to the way
that generics work and the way that they flow
into each other and all of that jazz,
it is amazing what you can get done.
It really, really is.
- Yeah and kind of on that same note of like,
letting your imagination run wild,
just subscribe to Matt's YouTube channel
because you make tons of videos talking about like,
really cool things you could do
with new TypeScript features.
I mean honestly, that's how I found out probably about
half of the cool new TypeScript things I find out
is your videos 'cause they're super short,
super easy to watch and like, they just kind of explain
really cool inspirational stuff like that.
So I highly recommend your YouTube channel for people
that wanna learn about those more unique things.
- This is like, my British kind of like sensibility
of inbuilt royal modesty is like cringing so hard,
but thank you so much.
I appreciate. - Yeah, no.
Another thing kind of, you had mentioned looking at like,
open source code, maybe even trying to like replicate
a small portion of it to start learning.
Would you say that maybe going into like Lodash,
for example, and trying to replicate those functions
would be a good stepping stone, 'cause a lot of them
use pretty complex generics but they're not so hard
that they're like miles above what
a normal application developer would?
Do you think that might be like a good kind
of stepping stone where they're small enough contained,
it's really easy to compare Lodash to your code
to kind of see if that's a good fit?
- Yeah, definitely.
I think, Lodash is an interesting example because it's got
a lot of, sometimes in like if you open up React
or like install React, you need to install React
and you need to install a types React package.
So the sort of like the TypeScript definitions actually come
in a different package and I'm pretty sure Lodash,
even though it, I think they come in the same package,
you just install Lodash and the definitions come down,
I don't think it's written in TypeScript.
It's like written in JavaScript
and then it's got some declaration files next to it,
and the declaration files are pretty complicated actually.
Like, I do go into some of those in my course
and they're pretty gnarly 'cause they've got their own sort
of language for like, a Lodash dictionary is apparently
a thing that sort of exports a type.
That, by the way, is something that you probably wanna like,
get good at if you wanna be even
a TypeScript application developer is navigating
through external libraries types, which is bloody hard
because they, it's like sort of entering Narnia, you know.
Like, you are entering into another world
and you're going, what the hell is declare interface
and namespace and all this sort of crazy stuff,
but if you just take a Lodash function and you go,
oh, I see, it's supposed to work like this,
supposed to work like this and you look at the tests
for the Lodash function as well, that's a really good idea
because you just get to go,
okay it's obvious that I can do this in TypeScript.
Why not, let's see how I would do it myself
and then compare it to the type definition.
Sounds brilliant, definitely.
- Yeah, and the nice thing about writing
your own Lodash function is generally your types
are gonna be simpler than theirs because they have to fit it
within their ecosystem and you're just writing
your own group by function.
You don't have to fit it into like a Lodash ecosystem.
So your types would most likely hopefully be
slightly, you know, simpler.
- Yes, and your function would probably be simpler too
because they have to worry about, 'cause they have to
support loads of old use cases,
which means their functions are really polymorphic.
So you need to take in lots of different values of things.
You know, you can either group an array or something
or group by an object whereas your functions
get to be less generic.
- Yep.
- Basically they can be, yeah, easier to use
and probably actually just take up less
bundle size as well, you know.
Why not, yeah. - That's true.
Yeah, that makes sense, and kind of when you were talking
about jumping into TypeScript code for like,
looking at the source code to try to figure things out,
I find that when I do that, the best thing for me
is to put on my blinders and really try to focus on
just the function definition for the thing I'm looking at
because there's so much other stuff in there that it can be
really easy to get overwhelmed, like you said.
So if you're just focusing on application development,
put on your blinders, try to find that one line
or two lines of code that describe what you need
and look just at that, and from there
you can branch out if you need but a lot of times
that'll give you all the information you need.
- Yeah, for sure, and just look at like,
yeah, I think when you get a sense for what's possible
and you start understanding all of the primitives,
all of the little pieces of information you need,
like okay, I understand how to add a generic function,
I know vaguely what a conditional type is,
then yeah it's amazing how you can put 'em together
and create something amazing, and also I would say
one more thing, no, probably plenty more things
but the next thing I wanna talk about is you need to be
a bit, when you grow in confidence with TypeScript,
I was, I've been thinking about this more
'cause I've been writing a book and like the section
of the book I'm on is on like thinking about
when to use any and use ts-ignore and ts-expect-error,
and that's a, I'm interested actually I wanna hear
your thoughts on it first because like that's a hard problem
because when you're a beginner you often don't know
when you should use any or when you shouldn't, right.
I wonder what you think about.
- My thoughts is if you're writing code
from scratch, starting with TypeScript,
don't use any anywhere pretty much.
I find that any is almost always a code smell
and it's you trying to get around the type system.
If you're changing a code base from JavaScript
to TypeScript, that's a little bit of a different story
'cause you are starting in a place
where everything is already any, but I think
if you're starting straight from JavaScript,
try to get as many anys out of there like,
or if you're starting, sorry, from TypeScript,
try not to have any anywhere in your code base.
If you have it, I find it's essentially saying,
you know what, I'd rather use JavaScript right here,
which is removing a lot of the,
even one any can destroy the confidence
of TypeScript in your entire project.
- Yep.
It's tricky when you get to the more sort of utils,
like I definitely agree in like the app developer stuff
for sure is like any any is gonna be bad news really,
because especially anys that like leak out of functions.
Like this is why I made like tsresets as well
to like make sure that JSON.pass as well,
we can get to that later as well,
but JSON.pass for instance, like leaks an any into your code
and then if you return the result of that from a function,
then the any sort of spreads everywhere, you know,
and like, just poos all over your code basically
and it's no fun to work with, and if you are though,
if you are adding those yourself
then it's not great in application developments,
but if you're in utils developments,
then often what you're gonna need to do
is you are occasionally gonna need to run into situations
where you are smarter than TypeScript,
where you know more than TypeScript,
because TypeScript is really good at inferring most stuff
when you're just at like an application level developer
sort of level of complexity, but when you get
into more levels of complexity or you're doing stuff
that's kind of really funky and you're pushing JavaScript
to its limits, often TypeScripts won't know
how to describe the stuff you are doing.
So you're gonna need to start using as a bit more,
start needing to use maybe as any or as unknown
as something else and needing to start to cheat TypeScript
a little bit and often knowing when to do that
is really important, and if you look at library code,
you're actually gonna notice a lot of as anys in there.
So TRPC, Zod, Redux, full of as anys, React Query too
because often the library maintainers just know more
than TypeScript does about the code that they're writing
and as long as that doesn't leak into the user's functions,
then it's kind of fine really and I think.
- Yeah, it's almost like a a black box at that point.
It's like, as long as the black box input and output
is correct, it doesn't really matter what happens
in the middle for the application developer's point of view.
- Absolutely, and often when you're writing utils,
utils are the place that you really want to unit test
as well 'cause you wanna make sure that's right
'cause they're the most shared code,
you're gonna get the most value out of your unit test
from that, especially anything involving
time zones or dates or something.
So if you do that, if you've got your unit test in,
then sure, you can be a bit looser inside the body
of the function, but you're absolutely right.
If the input's going in, produce the right outputs
on the type level and the runtime level, then if you add
as any in the function body, it is absolutely fine
in my book, and that can be a slightly uncomfortable
when you've come from application land and you land
in library code and you think, no this is all unsafe,
what are you doing? (Kyle chuckling)
- Yeah, it even makes me cringe a little about,
I'm like, oh every time I see an any I'm always like,
(Kyle groaning)
but yeah, it makes sense though.
- That's the right attitude, that's the right attitude,
but like sometimes you've gotta live with
the cringe, absolutely.
- Yep, and would you say that using, you know,
any type of casting to like, as any or as unknown,
that's like a last resort kind of thing
where it's like I know what TypeScript doesn't,
I know that this is going to be whatever it's gonna be,
I'm gonna manually do that cast 'cause TypeScript,
there's no way in TypeScript to do it essentially?
- There's two situations.
So if you know more than TypeScript,
like let's say you're doing a document.getelementbyid
and you're saying okay, the thing I'm getting
from document.getelementbyid, I know that
it's a video element, I know that and so it's gonna have
a source property, it's gonna have a stop, blah blah blah.
So because I know that and TypeScript can never know that
without me, because it just thinks that
document.getelementbyID returns element type or null,
so some sort of element from the DOM or nothing
if the ID doesn't match.
So I know that the video element is gonna be there
and I know that it's a video element.
So in that situation you can say HTML elements as
HTML video elements basically,
and that makes sense and that is providing information
to TypeScript that it doesn't know, right,
which is crucial and there's no other way around that.
You can't make that any safer.
- Yep.
- But if you are using it to suppress an error, then yeah,
you're in the sort of funny cringy territory, right?
Like I think I, a lot of people use ts-ignore
and ts-expect-error for those situations as well.
I'm wondering what your thoughts are on those.
- Yeah, I like using ts-expect-error
if you expect there to be an error.
I don't like ts-ignore as much because if you're using
the ts-expect-error to say like, okay I expect this
to have a problem because of whatever reason, you know,
you leave a comment explaining what it is.
If you use ts-ignore instead, it kind of introduces
the problem where well, maybe that error gets fixed
in the future and now it's no longer an error
but you're still ignoring it and you're ignoring
all the types checking on that one line,
so you could introduce a new bug
while the old bug was fixed.
So I like expect-error because that way,
if the bug does get fixed or like,
maybe TypeScript implements a new feature
where now you have type safety there,
you can remove that and now get type safety on that line.
- I think, I've been thinking about this
'cause I was writing this section of the book as well
and I was thinking ts-ignore and ts-expect-error,
they're really blunt tools.
They're like hammers, right?
And they target everything on the next line.
So let's say you have a line of code that's even just
more than 10 characters long or something,
you know that's got a few different nodes in there.
It's like const something equals something else.
You could get like maybe three things wrong in that line,
you know, and TypeScript is gonna yell at you
if it's only gonna, it's not gonna yell at you
if you use ts-ignore or ts-expect-error.
So I think in most cases you probably want to be using
as any if you can because as any just targets a single node
and usually it's just targeting the node
that you have the error on, right?
Just the little bit inside the AST that's going,
or the bit inside the code that's, where the error is,
and, but sometimes as any actually doesn't help,
for instance if you're calling like a union of functions
or something and you need like as never or something,
but yeah, I tend not to recommend ts-ignore
or ts-expect-error unless you are having some issue
with like a bad module import or something
or like something is really messed up
and you religiously need like, a Thor's hammer to come down
and just quash all the errors,
but like usually the as any feels more like a scalpel to me
that you can get in there and tidy things up.
- That makes a lot of sense.
I also like that because like you said, expect-error
or ignore just says this entire line is ignored,
you know, completely, while if you do the as any,
like you said, you're just changing that one section
of the code so you get type safety at least
around that piece of code which is nice.
- Yeah, for sure.
- Awesome.
Let me think here, what would be the, so okay.
I got a great question here.
So we kind of talked a little bit about maybe
how you can start transitioning to that more advanced
TypeScript developer role.
What do you think are some of the most important
more advanced features of TypeScript someone should start
with learning, because like usually you get to that point
where you're an application developer
and you understand the absolute basics of generics
and just like, the normal TypeScript features
but you don't maybe understand as many
of the complex features?
Which are the ones you think give you
the most bang for your buck?
- So you should start, I think like generics are interesting
because generics can be broken down
into just a few mental models.
I gave a talk a couple weeks ago in Romania where I said,
there's no such thing as a generic in TypeScript.
There is just generic functions, generic types,
and generic classes which basically just generic functions.
Yeah, generic functions, and so people talk about, you know,
like passing generics around, moving generics around
like passing a generic to this or receiving a generic,
declaring a generic, but really we're just talking about
type arguments and type parameters.
So when you think about TypeScript functions, in JavaScript,
functions can only receive runtime arguments, right?
In TypeScripts, they can actually receive
type arguments as well.
So if you say like, new set for instance
and you want it to be a set of strings, you don't pass,
let's say you're not passing any runtime arguments,
you just pass it like string inside the,
what are they called, angle brackets,
and so when you start thinking about that
you can think, oh, so I can declare a function
which basically receives a type argument
and that type argument is gonna tell it how to behave,
and when you get that in your head really like,
the whole rest of the thing just starts crumbling down.
You start understanding, okay so I can use extends
to like constrain that type argument so I can only
pass certain things in or I can make a default
with equals for instance, or combine those two,
and when you start treating them like runtime arguments
that just happen to be passed in TypeScript,
that's when I think, that's when it really unlocked for me
like oh my God, I can just make anything with this, can't I?
Like, I could just make just tons of reusable behavior
that I can use to just like, make everything a lot more dry
and a lot easier to use.
So I think really generics are the thing
that unlocks the keys to everything else.
There are a few other things if you want to get into more,
like type transformation, so transforming types
into other types, so things like using key of and type of,
it's like amazing and actually I think a tool
that application developers should use as well,
like being able to, you know, declare an object as const
and then grab the keys off it or grab the values
so that you've got like a strongly typed union based on
let's say, your environments that you're running your
app in, you know, production staging developments.
You've got like, a API based URL that's on each of those
and then you can just grab the environments off an object
which is already declared.
I mean, it's hard to explain without code,
but you get the idea. - Yeah, man.
- So, but generics is the first thing, I would say looking
into like conditional types and map types, especially,
being able to create objects from other objects
and yeah, just exploring everything that's possible really,
but once you take the idea that functions can receive types,
so much becomes possible.
- Yeah, yeah, and you mentioned conditional types.
I was actually gonna ask you about that next.
I first started diving into conditional types
because I was using Firebase for some code and I found
that their TypeScript definitions were really generic.
It was just like, it just takes a string
and I was like, I want it to be a key of my object,
you know, so I went through and I wrote a lot of
my own types for it and it required a lot
of conditional types like nested, nested,
nested conditionals like crazy.
- Yeah.
- What would you recommend is a good place
to start learning and practicing with those?
'Cause they're really confusing in my,
I think that's probably the hardest part about learning
advanced TypeScript, at least for me.
- Yeah, for sure.
Like, conditional types are funny because you often
won't need them that much, really.
Really what they let you do is is they let you do
if else logic on the type level.
So if you have for instance a generic type which can receive
another type, you could say, if I receive this sort of type,
do something or return a different type,
and if I don't, do something else and so,
hmm, I'm trying to think of like a really concrete example.
I think, like a lot of these features were added
to TypeScript to support different frameworks oddly enough,
and I can't remember why conditional types were added,
possibly to support like something a knockout
or something weird, you know like it's the kind of thing
where if you receive one sort of thing
in your function, it behaves a certain way,
otherwise it will expect something else.
So I can't quite think of an example on the fly.
I might need to dig out my course to see
if there's any materials that refresh my memory,
but like, oh what's a good answer here?
What were you using them for?
- So I was using them, it's been a while
but I was using them because I was doing like,
essentially queries with Firebase and you pass a string
for the different like, keys you want to query
and the string can be like dot notation
so you could say like address.street or just address.
So I think a lot of those conditionals were around,
is this a like multi-part thing like address.street
or is it just an address or is it like address.street.name?
Like, that I think was where most of
my conditional logic was around.
It was very complicated and I still
don't even know what happened.
I just got it to work and I was like,
okay, I'm never touching this again,
but I find that conditional types get a little bit confusing
because you can also kind of do a similar thing
in your actual application code, 'cause like let's say
you have a function that takes an ID that can be
a string or a number.
You don't really need a conditional type for that,
'cause in your code you just say,
if type of is number, do this.
If type of of string is do this, you know.
So I find that conditional types, they're really niche
but when you need them, that is the only solution
for that type of problem.
- Exactly, and they're funny too because the only way
you can declare a conditional type in TypeScript
is with a ternary.
So they can get very messy very, very quickly.
- Yeah, I.
- I sort of wish there was some kind of like, if statements
or switch statements that you could use in TypeScripts
with type logic, but TypeScript tends not to add
different syntaxes that do the same thing on the type level
just because there's so little
syntactical space to play with.
So yeah, we're probably stuck with like these
big nested ternaries for a while, which is kind of annoying.
- Yeah, yeah and I kind of understand why they did it.
I mean, that way it's all one line technically,
'cause if statements have a lot of white space and brackets
and parentheses and with a ternary, it's at least like,
continuous one line kind of thing,
but yeah, I hate the syntax, especially when you have like,
four levels of nesting, it's hard to read.
- There's like a, some kind of piece of code in Remix
that does this as well where I think they need to transform
some like input code into JSON or like,
deserialize some JSON, and so there's just this massive
like, 20 line like conditional type
or has 20 conditionals in it.
It just gets pretty bonkers, but if you look at it closely
it's sort of understandable what it's doing.
It just takes a lot of brain space to work it out.
- Yeah, definitely.
Okay, I got a few rapid fire-ish questions
because I know I'm taking a bunch of your time.
I don't want this to go too long, but the first one
is going to be, when it comes to writing out
your type definitions and storing your types,
what is your recommendation for like,
file and folder structure?
For me personally, I generally like to write my types
as close to where they're being used as possible,
but sometimes when you have a more utility-focused type,
it can be a little harder.
Like, what are your thoughts and opinions on that?
- Yeah, co-locate, co-locate, co-locate.
So always put them next to where they're used.
Do not use declaration files for this as well.
I sometimes see people, it's one of my most popular videos
actually is like, people put their types
in declaration files and then import them
to the rest of their apps.
That's not what declaration files are for.
You should not be doing that.
If you need a sort of shared point to have your .ts files,
just make a types.ts in like,
the relevant folder or something.
So I tend to be a bit of a folder structure heretic.
I tend to only just put it in source
and just have long file names.
- Yeah, okay.
That makes sense.
Another one, this one's a little maybe,
a little bit more complicated, can't quite
rapid fire through it, but what are your opinions on using
like, pick, omit, and stuff like that?
For me personally, let's say I have a to-do
and the to-do has an id, a name, and a complete field
and I have a form that creates a new to-do.
For me, I personally would take my to-do type
and omit the id and that would be the type for the form.
Is that kind of your thought as well?
- You want a single source of truth for your types.
If you have a concept of a user in your application,
you want there to be a single source of truth for that user,
and then when you have like user without id,
you don't wanna be redeclaring that
because that's gonna get out of sync super fast.
So that's like a massive leg up is
if you can just derive types from other types
because that's what you're doing.
You're basically making a flow where the source of truth
is up here and then everything is derived from it.
So yes, absolutely.
- Awesome.
Next thing is going to be,
I'm having a brain fart on what I was gonna ask.
- Quickfire answers, not quickfire questions.
What's going on? (Kyle laughing)
Come on, I'm ready.
- Where am I?
Okay, oh yes.
I remember it was when you talked about ts-reset.
So that's the TypeScript library you created.
I think it's really cool.
Do you wanna kind of maybe talk a little bit
about what it does and like your reasoning behind it
for people to understand?
- Sure.
So we were talking about anys earlier and talking about
how annoying it is that like JSON.pass returns any,
like if you call fetch and then you do like,
a JSON outta fetch, then it's gonna do a promise any,
so you leak these anys into your code base,
and TypeScript team themselves, because this has been
going on for a long time, like they don't want to make
a change where that would change to unknown
because that would break just tons of the ecosystem.
- Everything.
- Yeah, break everything and they don't wanna do that,
and that makes sense.
So I wanted to create a library or like, an install
where people could just install it and like,
do a little import and then it would just sort of
make that tighter and make JSON pass return unknown
and make fetch JSON return unknown as well,
and a few other things that I wanted to do,
especially like array.includes
for read only arrays and stuff.
So it's really nice like, it's gotten a really nice response
and it's got a lot of installs now too
and I think people like it because it just gives them
a little bit more safety, you know,
it just makes their TypeScripts just a little bit safer
and it's really easy to install only the rules that you want
or just install it once and boom, you're ready to go
and yeah, that's ts-reset basically.
- Yeah, I kind of view ts-reset as almost having
another config option that's like super strict
because strict gives you a certain level of strictness
and type ts-reset just gives you a little bit
of extra strictness around like,
those leaking anys like you said.
That's the biggest benefit to me,
and actually the array includes is really nice
because if you have like a as const array
and then you wanna check it to see if like,
a user input's included, I always hate that.
So ts-reset fixing that is a huge bonus.
- Nice, yeah, and also Boolean narrowing as well
is really nice too.
So if you have like a, if you have an array
and you want to just remove the nulls from it,
then if you were to just pass like dot filter Boolean,
like uppercase Boolean, then by default it wouldn't work.
It works on the runtime level but not on the type level.
Oops, works on the runtime level but not on the type level,
but with ts-reset it works perfectly.
- Yep, yep.
Yeah, no, I really like ts-reset.
It's a little bit of a buy-in because when you first
install it, like you're gonna get a ton of errors
but it's not too bad to fix those errors,
and honestly it's nice to see those
because those are errors that are kind of errors
that you just didn't know about before.
- Yeah, it's like when you turn like,
no unchecked indexed access true or something
which is like, I tend to recommend
two strictness config settings, which is strict true
and no unchecked index access true.
When you turn that one on you realize,
oh dear, I've been accessing all of these objects wrong
my entire life and now TypeScript
has finally caught up to me.
- Yeah, definitely.
Okay, two more questions.
The first one's gonna be a little bit more complicated,
second one's easy.
First question, when you're converting
a JavaScript application to a TypeScript application,
I'm not sure how much experience you have with this,
but what are your recommendations?
Do you like to do more of a bottom up approach
where you start with like, the util functions
and the lowest level hanging fruit,
or do you like to kind of start at the top with like,
okay, we're gonna start with the payment workflow,
just go down from there.
Like, what are your thoughts on that?
- Yeah, great question.
So I do have experience of this.
I did it a few jobs ago, I converted like a rec native app
all the way from JavaScript to TypeScript
and it was a lot of fun as well.
So I did it completely in the way I don't recommend.
I happened to be a solo dev on it, and what I did was,
I just did like a bash script where I just changed
all the files to Typescripts and added a ts-config
and it meant that when I did tsc-watch,
I had like 2,400 errors to fix or something.
I could do that because I knew that I,
my code wasn't being reviewed particularly,
like my lead was not really working on my project
at the time, so I could just force it through,
and that is just not something I would recommend.
The way that I would recommend to do it is
to first of all, add a ts-config JSON,
and then what you wanna do is you want to go through
your entire application, and by the way,
this is following a method that Century used
to convert their code base.
So Century, if you don't know, is a massive
error logging application and they have an open source
React code base that you can go and look at,
and it is massive.
It's bigger than many of us will ever deal with,
and they wanted to transfer over to Typescripts.
So what they did was they ran a script
and they worked out all of the files in their application
that had the most dependencies.
So if we think about, if we think about a application
as like a tree of files, right, and you think about,
'cause I was like when you said bottom up and top down,
a lot of people think of like a tree where
it's sort of going from the bottom up.
I tend to think of it like a sort of potato plants
where it's sort of going down like this.
So you have the files with the most dependencies at the top,
but nothing sort of depends on them,
and then you have stuff right at the bottom,
which is like all of the, let's say the screens
of the application or something.
Now they're never really important to other files.
They're like the leaves of the tree.
So I would recommend going from the stuff
with the most dependencies first.
Annoyingly, this is gonna be the hardest to transfer over.
It's gonna be your utils folder,
it's gonna be the stuff with the most generics.
- Yep.
- But then when you do the next one down,
it means that all of the stuff you're importing
is already strongly typed, and so by doing that,
it means that you are like, yeah you are basically sold.
Otherwise you are, if you do it the other way,
you are kind of fighting TypeScript all the way up
and you're gonna end up adding a ton of anys there,
whereas doing it the other way, even though it's gonna be
painful at the start, it's gonna be smoother as you go,
whereas the other way you might just end up with a bunch
of anys like, and it's just not gonna be fun for anyone.
- Yeah, that's the same thing I recommend as well
because like you said, you start with the utils file,
it's hard, but then once you get that fixed,
every place that it's used, you now have that type safety.
So the next file you go to upgrade,
half of it's kind of already done for you
because you have that TypeScript and as you get closer
to the edges of the actual tree,
it becomes really easy 'cause it's like,
well it's kind of already done, I just have to add
a type for my props and boom, the entire page is done.
- Yeah.
- So yeah, I like that approach a lot, and do you?
- I had to do it in small PRs as well,
which is key. - Yep,
and do you have any recommendations
for like setting up your ts-config for that?
Because if you just go straight in strict true and all that,
like you're gonna have about a billion TypeScript errors.
Do you recommend like maybe having a looser
TypeScript config at first to start converting things,
or like what are your thoughts there?
- So you should never ramp up strictness.
Ideally you should start at your max level of strictness
because there's no way to incrementally ramp up strictness.
You've either gotta go all in, and if you're working on
a project with other people there,
if you were to ramp up, if you were to like start at like,
strict false or something, first of all,
a lot of the ts libraries that you're gonna use
actually require you to use strict true like Zod,
and you know, TRPC and stuff, and if you then
ramp it up afterwards, you are gonna need one massive PR
to make sure your CI go stays in the green
and that's what you want.
You wanna do small PRs, you want your CI to stay green,
you wanna keep shipping while you're doing this.
That's crucial.
You can't just take, you know, four weeks off
and just get it done.
- It'd be nice.
- And so, it would be nice.
I have actually seen it done for different things,
but like not advisable, and so yeah,
you want the maximum strictness at the start.
I have a ts-config cheat sheet on totaltranscript.com
that you can go and have a look at.
There's also a repo called ts-config\basis,
which is basically just a set of ts-config examples
that you can use based on what framework you're using,
but also the framework you're using,
if you're using hopefully a framework,
will have a recommended ts-config
that you can just plop in, right.
- Yeah, that makes sense, and so when you're going,
let's say you're going to convert your util files.
So you're gonna take like formatcurrency.js,
convert it to a TypeScript file, and you're gonna leave all
the rest of your code JavaScript files, is that correct?
And then every time you touch a file to change it, boom, ts,
boom, ts, and so on?
- Yeah, exactly.
- Okay.
- And you tend to want to do it in two passes.
You tend to want to make one PR where you change it
to TypeScript and then another PR where you add a feature
that you're gonna add anyway, right.
You want that in two steps and yeah,
often you'll want to like change the utils folder
and then a couple of things that depend on the utils folder,
just so you can see your code working
and just so you haven't broken anything.
- Yeah, that makes sense, 100%.
Okay, moving on to the final question.
This one's super easy.
You have anything else that we haven't talked about
you think we should add?
Just any final words, anything like that?
- Good question.
If you like the way I talk, I guess you should go
to TotalTypeScript.com and, which is where I do
a ton of writing, I've got a ton of free tutorials,
a ton of paid stuff as well, which is good
for like your education budget and all that stuff
and I don't think so, man.
Yeah, I don't know, I'm gonna.
- Like, we covered a lot, so if you can't
think of anything, that's 100% okay.
- Cool.
Yeah, that'll do, I think.
- Awesome.
Well hey, thank you so much for coming.
I'll link his YouTube channel.
I'll link that TotalTypeScript
in the description for you guys.
I highly recommend you check it out.
Matt's like, the guy I go to for TypeScript stuff,
so he's awesome.
- Oh, cringe, but thank you so much, Kyle.
I really appreciate it.
- Yeah, thanks for talking with me.