The Weird Parts of TypeScript 13 exercises

Techniques for Triggering Excess Property Warnings in TypeScript

There are a couple of ways to solve this challenge, but first let's discuss why the @ts-expect-error directive isn't working as expected.

Here's the options object we started with:

const options = {
url: "/",
method: "GET",
headers: {
"Content-Type": "application/json",

Loading solution


00:00 Okay, so the access properties warning in TypeScript can be triggered in just a couple of different ways. The reason that TypeScript thinks that this is okay is that really, if you think about TypeScript's model, TypeScript's type model is structural. If we look at this and we say, okay, let's say we have a function

00:19 that expects some fetch options here. These fetch options, as we know, come from just here and we've got URL, method, headers, and body. But our options here have an extra property. They have an extra property of search. And so we're trying to pass fetch options and search to fetch options. Well, if we think about this,

00:39 nothing's going to error if we pass an extra property. So TypeScript, because it says, okay, it's got all the required features of fetch options. It's got my URL, which I do need. It's got the method. Like if we were to remove URL here, then we'd have a problem because a required property is missing. But extra properties,

00:57 actually TypeScript by default doesn't check. Now it will check them if you do a few different things. So if you say options, let's say it is give the variable a type of fetch options. Now it's going to check and it's going to say you added an excess property in this position.

01:15 Object literal may only specify known properties and search does not exist in type of fetch options. Another way to do this, to trigger excess property checks is to say satisfies fetch options. Great, we're getting the same error again. So search is saying may only specify known properties. Search does not exist on that type. Or the other time that you can do it

01:35 is when you actually inline this option. So if I say inline the variable, now my fetch is being passed URL method headers and search. And now because that const could be like, before it was declared outside of my fetch. And so we could be using for something else.

01:54 That's a reasonable assumption for TypeScript to make. But when we pass it directly to the function, then TypeScript is going to yell at us and say you can't pass search here because I know that you're only using, you're only instantiating this object anyway so that you can directly pass it to the function. So that means errors in that situation. But excess property warnings,

02:14 they're not present in TypeScript by default because of this mental model. And of course, because everything is kind of like assignable to this empty object type at the very top, then if you think about it, like if my fetch just expected an empty object, we would have exactly the same behavior. So if options were just an empty object,

02:33 sure, we could pass anything we wanted to in there. So you should think about TypeScript's assignability model like that. It's not about exact objects being passed around. It's TypeScript thinking, does this satisfy this contract? Do I have all the required things I need in fetch options? Sure, there's an excess property in there,

02:54 but if it's not like inlined into the function call and it's not being declared with that satisfies or a variable annotation, then fine. Yeah, sure, like I won't check that excess property for you. This ends up being more useful than you think it is. If we were to look at flow,

03:12 which does do excess property checking, then it can be a little bit of a pain in the ass because you'd need to remove this search manually before you pass it to my fetch. This is, I think, a good choice that has been made by the TypeScript team. And it means that, sure, while it doesn't behave how you think it should sometimes, you generally learn just to inline your variables

03:33 most of the time. So this is, in general, what I do. If you're used to declaring things inside const, then maybe you need to get out of that habit so that you get better errors when you actually do pass this stuff around. So when you can, I would say, annotate your variables. When you can, even better,

03:51 just pass them directly into the function calls. And make sure that you're aware that excess property warnings don't always trigger in TypeScript.