TypeScript has been taking over, and for good reason. Have you ever had good end-to-end or integration tests that ended up being invaluable during some refactoring? You can think of TypeScript in the same way. It's amazing when it's done well, but when done poorly it's misleading and a huge headache.
unknown and moving on if you're unsure.
In this episode, you'll learn more about these realities of working with TypeScript and why it's all totally worth it!
TypeScript Users: Take a look at five interfaces you wrote recently and see if you can refactor them to make them easier to read for the next person.
Non-TypeScript Users: Think about the last complex code that you had to interface with and whether it would have been easier to interface with if it had types.
Kent C. Dodds (00:00):
Hello friends, this is your friend Kent C. Dodds. And I am joined by my friend Daria Caraway. Say hi Daria.
Daria Caraway (00:07):
Kent C. Dodds (00:08):
I asked you to say hi Daria, but that's okay too.
Daria Caraway (00:11):
Kent C. Dodds (00:13):
Daria Caraway (00:46):
Yeah, it's a great question. I talked a little bit in the last episode about not being able to say no. So I think that's why I would do that is the opportunity was there and it seemed like less friction to just figure out how to do it and do it well. But I think that those experiences probably contributed to a lot of my passion for this topic specifically that we're talking about. So I'm sure that'll come up.
Kent C. Dodds (01:15):
Yeah, absolutely. I can only imagine. So actually I'll just say I have done migrations before and every single time it was just easier to migrate when there were well-written tests for the thing that I'm migrating from.
Daria Caraway (01:33):
Kent C. Dodds (01:33):
And a well-written test is if there were very low-level unit tests, you pretty much just delete them. But if you had some good end-to-end tests or integration tests, and then those were invaluable and I think TypeScript as a lot of the same way. So I imagine as you were doing these migrations, you just really wished that the old code had been written in TypeScript, right?
Daria Caraway (01:58):
Kent C. Dodds (01:58):
Daria Caraway (02:12):
Kent C. Dodds (03:28):
That actually sounds a lot like tests, right?
Daria Caraway (03:30):
Kent C. Dodds (03:31):
Daria Caraway (04:07):
Yeah, I think... oh man, this is kind of a hot take, I think. But I like to think of TypeScript in my own brain as being kind of like a fake type to language, if that makes sense, in that I've done some backend work. We talked about it a lot in the last podcast, in a Java type language where when the types are there and your code compiles and it runs, 99% of the time, you're good to go unless that data is coming from somewhere else, your types are going to be true and they're going to represent the data that's there. And the code that actually comes out, that is compiled takes those types into consideration when it's thinking about doing memory usage and allocation, right?
Kent C. Dodds (05:41):
And then you can come back later and fix it when you are good at TypeScript. But I think that when you aren't experienced with TypeScript, it's really easy to say, "Oh no, that will never happen." And just slap a, what's the bang you put at the end? Non-null assertion?, Or say as whatever and say, "I don't want to handle that error possibility." So I'll just say that will never happen, TS ignore next and all that. You can totally do that, but lying to the compiler is not advisable. It can really make it hard for TypeScript to be useful to you.
Daria Caraway (07:21):
Kent C. Dodds (08:22):
Daria Caraway (09:31):
Yeah. Yeah. That's a really good point. It kind of made me think, in the past, before I used TypeScript, I used CoffeeScript and that's a whole other thing.
Kent C. Dodds (09:31):
Daria Caraway (09:40):
But yeah, I would also use the test to learn more about the code, right? If you don't know how you're supposed to use a component and there are no documentation, there's no comments or anything, you can take a look at the test to understand, okay, here's how, here's what I'm supposed to pass into this component, here's what I can expect the component to return to me. Right? And in some ways, TypeScript kind of takes that burden off of the tests a little bit in that I don't have to rely as much on the test for documentation because that layer of it can be provided by that static analysis tool in this case being TypeScript.
Kent C. Dodds (10:19):
Absolutely. And when we're talking about documentation too, it lends itself pretty naturally to documenting things as well, where you're like, "Well, I'm writing this TypeScript anyway. I'll just add a couple of notes to explain what this is, or we have this user type that we just use all over the place." And actually I've been writing a lot of Discord Bot code recently for my Discord and they're TypeScript, everything is TypeScript and it's really nice. They have a member object, that's a member of a Guild, but then they also have a user object.
Daria Caraway (11:35):
Yeah, I think it kind of depends for me on what I'm working on. I'm more of a product developer. So a lot of the times people aren't consuming my code. Right? But there are other people on my team that have to read it. But in your case, you might be working on libraries more often that other people are consuming. When I find myself writing something that I know is going to be used by lots of other people, for maybe a variety of different reasons, I'll be more likely to add real documentation on top of the types. When I'm working on something for the product I work on that I know just my team's going to have to work on, or I'm going to have to work on in the future, I put only as much context there as I think is needed, knowing that you already have the context for our entire application and what this data may or may not look like.
Kent C. Dodds (12:25):
Thank you for such a nice view on this. One of the things that bothered me so much with when I was doing Java and granted, this is the early days of my career, but all of the code that I saw was you'd have all these getters and setters and you'd have a user object with a getName and a setName, and then you'd have Javadoc there that would say, "Here's the setName and here's the name." And the Javadoc would be, "It's the name, like the name for the user or whatever." It's like, "Duh, I don't know why you needed to write that." So yeah, I totally agree with that idea of practicality here, you don't have to document every function.
However, there are some internal functions that only I use that I've decided, okay, I use this enough and it's a utility or something where it can be nice for me to remind myself, okay, we're making a relationship between two users. So this is user A who's going to be making relationship with user B, whatever it is. And so, yeah, being practical about how much you document things is useful. Having TypeScript just in general makes a huge class of problems just disappear. But as you mentioned it, there are some ways to do that more effectively than others. And I'd love to dig into that a little bit deeper on, yeah, experiences that you've had where the TypeScript types were really great and helped you a lot versus not. And what the difference was in those two?
Daria Caraway (14:08):
Yeah. I think I have one example. It's easier for me to come up with an example of where I felt burned, because I feel like those examples stick with you longer.
Kent C. Dodds (14:17):
That's the negativity effect.
Daria Caraway (14:20):
Yeah, Exactly. I remember when I was migrating from AngularJS to Angular2, there was one, component is the wrong word, like section of this massive form. And it was all in TypeScript. But wrong is the wrong word. We were talking about how you can lie to the type checker. Right? So when I make a cognizant decision to lie to the TypeScript type checker, which I don't do often, but when I do, I like to think that it won't be a problem in the future, but when you're working on somebody else's code, because you're working on a code migration, when the types are lying to you, it feels personal. It feels like the person who wrote these types wanted to lie to me. And it's really frustrating. That's obviously not how it is, but when you're really in it, that's how it feels.
Kent C. Dodds (15:17):
Daria Caraway (15:19):
And so, yeah, there was definitely one instance where there was a massive object that came back from the server. It probably had like 50, 60 properties on it and they were all optional because we weren't sure what data actually lived in what objects, and that object was used in a lot of different places when maybe those should have been different types if that makes sense. So they were all just using one super generic type where all the properties were optional.
Kent C. Dodds (16:38):
Yeah. Yeah. Yep. Yep. You double on the lie.
Daria Caraway (16:41):
Kent C. Dodds (16:41):
I totally know what you're talking about and what makes it frustrating is you know that if they'd spent a little bit more time on it, or if you in the past had spent a little more time, I'm speaking for myself here. If I'd spent a little bit more time on it than I could have given myself a really nice interface and I could have said, okay, so we do know these are optional, but if this property is defined, then I know that these other properties must also be defined so we can union this stuff and make it work so that all I need to do is a little assertion function here. I absolutely know that this is the way that it is and now I can, all of those properties are required or whatever. So yeah, I mean TypeScript is not perfect, but it can allow you to provide really nice interfaces. You call them human considerate TypeScript interfaces. And so, yeah, can you mention just a couple of the tricks that you can use to make your interfaces more human-friendly or considerate?
Daria Caraway (17:51):
Yeah, I think what you just mentioned like all of the steps that could have been taken in this scenario to make these interfaces more considerate, a lot of them were what people might perceive as more advanced TypeScript types. Right? It's gone beyond just like the string Boolean situations. It's gone beyond just like an interface that has properties on it. And so I think like just the number one thing that I've learned is valuable is to, when I see a type that I don't know, when I come upon a type for a third-party library that sometimes in my experience, third-party libraries sometimes have really ridiculously in-depth types because they want to make it easier for the consumer to not really have to worry about what's going on. They just know that they use it and it is correct.
Whenever I come up on one of those types, I'm like, "Oh God, I don't know what this is. It has too many letters in it. There's lots of NET statements." I want to just close out of the file and be like, "I don't have to know what's going on here." I try to figure it out, break it down so that I can learn what these strategies are that people are using to make their types more considerate. And I think sometimes that can be really intimidating, but just being curious and yeah, steadfast enough to be like, this is confusing. I'm going to figure out what it is so I can learn what some of these more advanced types are. And then once you do learn what some of these more advanced types are, it becomes much easier to take what you have in your head and put it onto the paper, the computer.
Kent C. Dodds (19:33):
Daria Caraway (20:19):
Yeah. That's a really good point. I find that happening a lot where it's like, if I can't easily take what I know to be true and put it into the types, it's probably because it's too complicated or there's something that I have in my head that isn't explicitly spelled out with what's in front of me.
Kent C. Dodds (20:36):
Yeah. Yeah. That's a good point. That example of this giant object where if this property is to find, then these will be defined. That actually makes me wonder if the data model could be changed in some way to say, okay, well, rather than having this property in all of these, we'd have this Subobject, and if this Subobject exists, then clearly all those would be defined or studied, which is sometimes difficult to do. There are different, oh, what's the word I'm looking for? In TypeScript documentation, they have this pattern called, you might know what it is, but you have a kind variable or a property on the object, that is like an enum. So then you can switch off of that kind of variable. So this is like, do you remember what that? I can't remember what that's called.
Daria Caraway (21:32):
Yeah, I think you're talking about discriminated unions.
Kent C. Dodds (21:34):
That's it. Yeah. Yeah. Whether they're patterns or built-in features, just like what you said, digging into library TypeScript definitions and things can really help expose you to different ideas and good ways to manage your types so that you can make it more human-friendly.
Daria Caraway (21:59):
Yeah, definitely. And to just like...Sometimes I just need to take a step back. When I'm working on my product code specifically, you have so much context in your head, maybe you've been working on this feature for two weeks. You have so much knowledge, so much context. And I personally am going to come back to in a month and have completely forgotten all of that. So sometimes it just takes like, if I come back the next day and I really look at these types, is there something that is less clear now that I don't have this context right in the front of my brain?
Kent C. Dodds (22:34):
Yeah. I often find when I have that context hidden in my brain, I try to be really clever.
Daria Caraway (22:40):
Kent C. Dodds (22:40):
It doesn't, I think clever and TypeScript do not really go well together, like constructing these objects in a funny way or whatever. It's often better in TypeScript, I've found, to just be very explicit and very simple, it's okay to have a bunch of IF statements in here to check different properties, whether they exist in and throw an error. Often I will say, "That'll never happen. This is impossible." And so, that's where you're like, "Oh, I'm going to just add the bang and this will never be null." Then you find out later that it could. And so I'm always just happier or better off saying, "Well, if that ever does happen, then throw an error or log a warning." And yeah, I've definitely seen those warnings pop up like, "Oh." It'll say, "This should never happen."
Daria Caraway (23:31):
Yeah. That's funny. You kind of reminded me sometimes people will ask me, "Hey, I have this thing. I can't figure out how to type it." And maybe there'll be five other people in the conversation trying to figure out how to type the thing. And then you take a step back and you're like, "Why are you doing this?" And maybe that's the question that needs to be asked. It's so difficult to type. We have five senior engineers trying to figure out which complicated set of advanced TypeScript features, we can string together to make this thing work. Maybe this isn't the best way to do this. And I think yeah, I like that perspective sometimes.
Kent C. Dodds (24:13):
Yeah. That's actually really great too. Maybe we shouldn't do it this way if it's really hard for the TypeScript compiler then. And the TypeScript compiler is not perfect, there are definitely imperfections in the feature sets and everything, but if it's that hard to do, then maybe a user of this code that you're writing will also have a hard time, because of the API that you're exposing. Now, I want to ask you, this is barely tangential, but I thought about it a little bit ago. I want to know what your feeling is on single-letter variable names or type names in Generics. How do you feel about this? This is like pervasive and even the TypeScript documentation, everybody does it. I want to know what you feel about that.
Daria Caraway (25:01):
Yeah. I think I don't have as much of like a guttural reaction to it. I think because the first language that I learned at university was C++, it was the CS101 language. So the first place that I learned this concept of Generics is with C++ templates I think they're called. And they always use T and I think that the T stood for template. So in my brain, I've always just associated that generic value with the letter T, but I can see how, if you don't have that already wired into your brain, that it can get really confusing. And I've seen a lot of third-party libraries will do this for really complicated things. When you start to use more than one single-letter generic, like if you're doing a KV, KeyValue type thing, and there's six, then that starts to get very overwhelming. I can no longer just say, okay, the T represents a type. I have to keep in my head which one is which. So I think yeah, I don't know. It's hard. Well, what's your take on this?
Kent C. Dodds (26:10):
I hate it so bad. So if it's just one and it's T like sometimes you have an array and this is the type of the elements in the way, I think that's pretty obvious. That's mostly a familiarity issue. You figure out what that is and then you move on with life. But yeah, like you said, if you have even two and it's not totally obvious which one. Like KV, yeah, maybe KeyValue, I guess. But my big issue is, even if it was just KeyValue, why can't we just write the word key and value? I don't know why. Pretty much everybody agrees that variable names should be fully spelled out. But for some reason in TypeScript, all the generic types are just a single letter, like, "Write the word." I don't know. Anyway, that was just kind of an aside. But yeah I feel like there was one other thing that I wanted to mention and I can't remember what it was. Was there anything else that you wanted to bring out before we wrap up?
Daria Caraway (27:14):
I'm kind of curious from your perspective, I've been talking to one of my coworkers who works on the component library at the company I'm at, and the way that he thinks about TypeScript is inherently different than the way that I, as a product developer, think about TypeScript. I like to think of considerate TypeScript types being like, you can look at it and understand what it's saying, but sometimes in the library world, it might mean something really crazy complicated, but the user never has to understand it. They just have to know when they're using the component, they can type, they get auto-complete. It all is just like a really nice interaction. So in some ways that might also be a considerate TypeScript type. I'm curious from your perspective, which one you find yourself doing more of in the type of work that you work on?
Kent C. Dodds (28:07):
Yeah. Yeah. Library and application TypeScript are pretty different. Mostly because if we remove the types entirely and just think about what the software is doing when you're making a library for it to be useful, it needs to be generic. That's just like, otherwise, why are you writing this library? And so therefore it needs to satisfy more use cases. And so typically library code just in general will have more things that it's doing for you. And for that reason, when you add types to it, the TypeScript just tends to be a little bit more complicated, just naturally out of the fact that this code is doing more for you. And so I said that it's kind of different, but it's really not. It just comes down to the fact that you have to account for more cases, you ended up doing more unions or keyof, and all of this stuff to make things human-friendly.
Yeah. I don't really think of product versus library. When people look at my types, all I really care for them to know is how to interface with what I've built for them. I don't really care or actually really want them to understand what's going on under the hood necessarily. When I'm doing application types, man, for those that just seems to go by so fast. I'm just like, okay, this is a number, this is string, oh, well, this user, I don't know. It typically is a little less complicated, but I don't often think about things working or how my types describing kind of how things work under the hood. I'm curious what, could you expound on that perspective a little bit more?
Daria Caraway (30:03):
Yeah. Yeah. That's a really good point. I think from what I've seen talking to my friend who works on this library, it's what you said. The user shouldn't have to worry about what's going on underneath. They just want to know that they can use the component. It's easy to use. When they pass in the wrong types they get a friendly error message back that allows them to iterate. Whereas I think sometimes when I'm working on a product, I do want my teammates to know what's going on under the hood without having to dive in deeper. Maybe you have a really big component that has a bunch of little components under it. If you can just look at the type for the big component and get a cursory glance and understand exactly what's going on 10 layers underneath it, I think that's a win. So in that case, it might be a different way of thinking about TypeScript between those two modes.
Kent C. Dodds (30:53):
Yeah. That makes sense. Well, I remembered the thing that I wanted to talk about just to wrap this up. And you mentioned to me that you like to think of TypeScript as, or you want to make sure that people understand that TypeScript is not changed the behavior of your code, that the types are separate from the runtime. Can you expound on that a little bit?
Daria Caraway (31:17):
Kent C. Dodds (32:39):
I think a critically important point for people to understand is that the type checker and the compiler are two different things. And I actually waited to use TypeScript until I could use Babel with it. And that's why I started with Flow type, was because I didn't want to get rid of Eslint and Babel and everything else. And so yeah, it was a glorious day when Babel supported TypeScript. I was very happy about that and I definitely see them as two different things. I run TSC and that's my type check script. And I say, noEmit, don't actually compile this stuff. And then use Babel exactly as you described. Well, great. Is there any last thing that you want to bring up before we wrap up this episode?
Daria Caraway (33:23):
I don't think so. This is been a great talk. I've appreciated talking to you about it. I think sometimes this is a hot topic. TypeScript is kind of like, there're some opinions on whether or not it's good or bad. And so I appreciate talking about it and maybe it's not good or bad. Maybe it's just, we need to figure out how to use it in a intentional way.
Kent C. Dodds (33:46):
Yeah. Yep. I agree. It's one of those tools where it's not quite like prettier where it's like, "Why aren't you using this thing?" But it's pretty close. Most of the time you should probably be using TypeScript, but yeah, there are trade-offs for sure. Okay. So here's our homework. We actually have a two-parter for you. So because there are probably two types of people listening to our episode and that was not a TypeScript joke, but I'm going to run with that. There are two types of people.
Daria Caraway (35:08):
No, that sounds good.
Kent C. Dodds (35:10):
Awesome. Well, it's been such a pleasure chatting with you. I just loved visiting with you for the last hour and a half for these episodes. So thank you for giving us your time and you know what I forgot to ask you at the end of the last episode. So sorry about that, but what's the best way for people to get in touch with you online?
Daria Caraway (35:30):
For sure. I'm just @dariacaraway on Twitter. Also, if you go to dariacaraway.com, you can see any conferences that I have upcoming if you want to catch me there.
Kent C. Dodds (35:40):
Cool. And I hope that in the near future, we'll be able to meet in person at one of those conferences. Let's keep our fingers crossed, wear a mask and get vaccinated. Okay, cool. Thank you so much. And we'll see everybody later.
Daria Caraway (35:54):