Check out a free preview of the full Enterprise TypeScript course
The "Forbidding Implicit any" Lesson is part of the full, Enterprise TypeScript course featured in this preview video. Here's what you'd learn in this lesson:
Mike demonstrates the next step in the TypeScript conversion, which is disallowing implicit any types. Setting the noImplicitAny flag to true for an entire project can be problematic since there's a wide surface area for changes. Instead, creating a new tsconfig file in an individual subfolder and starting there can make the process more incremental.
Transcript from the "Forbidding Implicit any" Lesson
[00:00:00]
>> Alright, step three, the third step we're gonna take on is forbidding implicit entities in our project. So I'm gonna close all my editors here just to give us some more space, that's great. We're gonna go to our TS config, and we're gonna say no implicit any true, so there's just like a negative there, right, we're saying forbid implicit entities and let's just run your own type check.
[00:00:42]
Whoa, that's a lot 59 errors and 20 files. We're gonna go to each of these 20 files. No, we're not going to do that. This is a great opportunity to demonstrate how we can look at something like this and say if it seems too big, it's more than I want to take on in a poll request.
[00:01:00]
We're gonna make this change locally like within one folder and demonstrate how we can we can do that. And, it becomes more approachable that, you can do this across many days or many weeks, however long you want to take but it lets us have a tighter TypeScript config just in one part of our app.
[00:01:22]
So I think where I wanted to have a start was the utils folder again, principle of starting with low level stuff first, right? So in our utils folder, I want you to create a new TS config. This is gonna be very similar compared to what we did when we had a TSconfig in our tests folder, and we're gonna start out the same way.
[00:01:49]
Not exclude, extends this is a new feature by the way, relatively modern TypeScript feature. You can extend multiple things now. So you could have a TS config that just represents your strict type checking stuff, and then one that sort of represents, let's say you have different module formats that you're publishing, you wanna build common JS and then you wanna build ES native ES modules.
[00:02:21]
So it's sometimes useful to be able to extend for multiple things, they just sort of layer on top of each other in order, so why not? We'll use that syntax even though we have one. But this is another helpful thing for large, large projects that you can sort of break your TS config up into pieces and compiler options, just one thing.
[00:02:47]
We're gonna remember to turn it off to have it no longer turned on in a root-level TS config, comment that out. One more line, include this folder, all right? So once you've done that, we can run a command that will just type check within this one folder. Tsc -P for project, src/utils so we're just saying like just just type check that one folder for me.
[00:03:26]
We're just trying to get that happy with this more strict role. Did I put this in the wrong place, U-T-I-L-S, tsconfig.json. Did I save it?
>> I think you're in utils in your test folder.
>> [LAUGH] I am, thank you. Oops, we'll put it in the real utils folder.
[00:04:05]
This is why I create things on the CLI [LAUGH] ,so that I don't get screwed up like that. All right, there we go, that's a computer doing things. All right, we got 11 errors and 4 files way better than 59 errors across 20 files. So great, so let's look at networking like what's happening here.
[00:04:26]
We should really be in this mode are focused on a specific task. We're putting reasonable type information in place if it's easy to understand what a type should be forgiving value. Or if it seems complicated and we don't know the answer yet, we kick out and we say, I'll put an explicit, any there.
[00:04:51]
That's totally fine in this step you're just trying to get rid of the implicit eddies. So this, we got some types here. There's my request info and my request a knit, which should be optional. And I can delete this stuff now, it would compile if I left this in but just for readability, I don't like to leave both types on the code and types in the comments.
[00:05:22]
They can get out of sync super easily and cause many WTF, so okay that's networking. And I'm just looking at these, see this get Json get Json, so those are probably things I already just fixed. Let's look at errors.ts, error description I have isn't any here. It looks like what I'm doing is just like persisting it as.
[00:05:49]
Serializing it into a template string and it has the word description in it. So I'm torn here I could put in any that's technically what's in the Jas doc types or I could put a string. I think I'm gonna put a string here. It's a fairly small risks to take then error, let's look at this, stringify error value.
[00:06:15]
We're checking to see if it's an instance of error. So at the very least, it could be an error but also something else, because why would I have to check to see if it's an instance of error, if I knew it was an error coming in? I'm gonna go ahead and say, let's see if an unknown will work well here.
[00:06:35]
Can someone tell me what unknown is?
>> Is it generic?
>> Not quite.
>> It's exactly what it sounds like. You don't know what it's gonna be, so we have to check.
>> There you go it's kinda like any but it's opaque, we can't use it until we check it out like until we inspect it use a type card like this right here it's unknown and down here, white.
[00:07:07]
There it's an error, so here it's unknown, here it's an error, right? Now we're in the else from the unknown. So we know it's not an instance of error, but TypeScript doesn't give us a good way to say, look, it could be anything, but not an instance of error.
[00:07:22]
Like there's no way to describe that in TypeScript. Cool, so maybe that worked out. We'll have to see how the tests work out here. Here we've got error, we'll use that type here. And the return type is a string, so I'll put that in as well. I like explicit return types on my function.
[00:07:45]
Okay, making some progress here. Let's look at deferred, just more JSDoc stuff I got to copy down. It would be totally appropriate for some of these if you just didn't have the JSDoc information here, you could just make it any. You might choose to do that if I'm moving a little fast for you here.
[00:08:05]
There's plenty of stuff, plenty of extra steps, right, this is like the path I gave you. It's an 11 step process where eventually you're gonna get bugged about explicit NES and then you could come back here and provide a more explicit type. We're really just trying to get a new compiler setting in there and then squash the errors and then you just trust that as we turn up the strictness both in ESLint and TS config.
[00:08:37]
Anything that you sort of like punted on where you just said, they could put it any here. Eventually you'll be bugged to get rid of that. And run this one more time. So you were at, not quite api.ts. What do we got going on here? An interesting one, here's another example of me using like a more complicated type in JSDoc.
[00:09:03]
I've got like an object type here with fields on it. So I'm just gonna bring this in, get data, I'm gonna bring this object in, I'm gonna have to delete all these asterisks. Cool, so there's my options object. Promise is yelling at me here, I'll get to that in a second.
[00:09:38]
Let's look lower here, there's an implicit any. Okay, I'm gonna choose to make these enemies like I don't know what JSON data is going to be, I have to look pretty hard at this promise, it's a very abstract thing. This is like something built on top of React use effect that goes and fetches data for me and set some state using a state senator that's passed into it once the API calls come back up It's not obvious, looking at this, what these types should be.
[00:10:15]
We're going for any, it's fine at this stage. Don't let the perfect be the enemy of the good. And then finally, promise takes a type param here, and we're not giving it one. What should we give this promise as its type param?
>> I gave mine unknown.
>> Let's try an unknown, hey, appears to work.then.
[00:10:47]
So, this is coming in, actually I can delete these now, I bet, let's see. Yep, JSON data is an unknown, it's no longer an implicit any, get rid of this one too. Awesome, that's great, and again, we have just been, we've been working with type annotations, function signatures.
[00:11:13]
We have not really altered the way code works at all here, that's your goal. It doesn't always turn out this clean, but you wanna minimize the degree that you have to change code. We did change it a little bit. You remember when we this used to be if, like, AppContainer triple equals false, that's the kind of thing where if you do like 50 of those in one PR, a couple of them will screw stuff up.
[00:11:38]
Because it feels like you're just playing Whack a Mole. And you can think of it as, I'm making all of these things better, all 50 of them, but somewhere, someone was passing in a zero or an empty string and they thought that it's better to sort of be more careful about that.
[00:11:58]
And to try to add the type information, and then later you can come in and alter the behavior more carefully, see where we're at? Great, type checking passed. So we got a couple choices here and for the purpose of time, I'm gonna have a bias towards one. I'll just tell you what I would do next.
[00:12:28]
I would make a commit, open a PR, get this merged in, make I would take this tsconfig.json and I would literally just drag it into this data folder where we would start to add more type information. And I would just do another PR straight from there. It's a lot more of the same stuff that we've been doing.
[00:12:50]
I'm not sure it's super valuable that we all go through and do it together. Now here you can see, like this is an untyped thing. It's an object that needs a type. It's being used as a dictionary apparently. We could totally get in there and do that, but it's just a lot of the same of looking at the implementation of the function, how's it used?
[00:13:13]
What's a safe type that I can put on it? Let's not perturb the way the code works. Let's just describe the types, check it in, move on. And you could ask you could add this command to your CI or something you'd say I wanna check this in and I also want the bill to only pass If everything in this folder continues to work this way, it continues to pass with this tsconfig.
[00:13:41]
Why that's beneficial is somebody else might be currently working on adding something new to the utils folder, and you can protect your progress by checking in that tsconfig and by making sure that this runs. All right, it means that, okay, they're gonna they're gonna check their code in they're gonna open up a PR like this is where depending on how you have things set up it could fail and everything's merged together.
[00:14:08]
But if you force people to rebase before merging their PR, they'd get in your change, CI would run, it would fail. And then they'd be, they'd say, it looks like I have to change some things here, I'm gonna make you commit and we'll move on. And if you want more practice, feel free to go in and do a little more conversion, right?
[00:14:31]
It just takes a little while longer.
Learn Straight from the Experts Who Shape the Modern Web
- In-depth Courses
- Industry Leading Experts
- Learning Paths
- Live Interactive Workshops