Using `as const` and Indexed Access Types to Extract Keys and Values from a Type
Let's work through the solution step-by-step.
Updating BackendStatus
First we need to update BackendStatus
so we get the value of the keys. We can do this by using keyof typeof
to extract the keys from BACKEND_TO_FRONTEND_STATUS_MAP
:
type BackendStatus = keyof typeof BACKEND_T_
Transcript
00:00 Okay, let's do this. We know that we can extract the keys for this backend status. This is gonna be relatively easy because if we hover over this, you can see that all of the information that we need is there, 0, 1, 2. We can actually use a technique that we've seen before. Key of, type of, backend to frontend status map. Now backend status, boom.
00:19 First test passing, this is great. But how do we grab the pending success and error information from this? Because it's not even being inferred here. Now the reason this is not being inferred here is because TypeScript is actually being very smart about the way that it's inferring this.
00:36 These can actually be mutated. So if we say backend to frontend status map, 0, we can just change it to something else because this is how JavaScript works, right? Like this further down line could just be mutated into some strange thing. But we could change that.
00:53 We could actually make this read-only if we want to. We can do that on the runtime level if we want to. We can say object.freeze here, which is just something that's available in JavaScript. And now TypeScript understands that this is actually read-only and it's got pending success and error inferred here.
01:12 And we can no longer change it because it's a read-only property. This though is not what I recommend because this actually operates on the runtime level. Wouldn't it be great if there was a type-only way that we could do this? Well, there is, and it's called as const. So this is you saying to TypeScript
01:30 that this object that I'm declaring here is not going to change. It's a deep constant. And it means that anything that you put in here can not be changed at all. Whereas actually object.freeze only works on the top level. So here what we've got cannot assign to zero because it's a read-only property.
01:48 We hover over this and we've got read-only zero pending read-only one success, read-only two error. Beautiful. So now we've actually got all of the inference that we need and we can actually take this and grab our success pending or error. But how are we going to do that? Well, we can use something called an indexed access type.
02:08 So let me grab this out into its own type just for clarity. Let's go type backend status map equals type of backend blah, blah, blah, blah, blah, blah. Can't talk and type at the same time apparently. There we go. Backend status map. Now we've got this grabbed out as a type
02:25 and we can actually do key or backend status map here. That's lovely. Now frontend status, this is going to be backend status map. And ideally what we want to do is kind of index into it and grab out a type from in there. And we can do that via an indexed access type.
02:44 So we can actually grab, this is now pending because we're grabbing zero from backend status map. So in order to grab success or error, we could do a union here, right? We could do backend status map one or backend status map two. And now it's kind of working, but this is pretty horrible.
03:03 Wouldn't it be great if there was a way that we could actually like just grab all of them? Like we can actually reduce this a little bit already because you can actually pass in a union into this member here. This is why it uses this syntax instead of let's say like this, for instance, because it's just a little bit nicer.
03:21 So what we can do is we can actually grab one and two inside here, and we can actually pull those into there. And now frontend status is pending success or error. If we only grabbed zero and one, then it would just be pending and success there. But because we're passing all of them in,
03:42 then we could do that. But we've got backend status right here. So why don't we just take backend status and put it inside there and see what happens. Backend status. Ooh, now what we've got, is it still pending or success or error? Because the way that these keys work, by the way,
04:01 is you can either index into them with a number, if it's a numeric key, or a string representation of that number, because this is how JavaScript works as well. It's slightly funky. But essentially what we've got here then is we've got backend status. We're using each of these to index into backend status map,
04:20 which is then returning each member of that union in there. We could also do backend status or two, and this would be an error actually, or it should, oh no, sorry, or three. And this would be an error because three does not exist on this object here, unless we were to add it, let's say. But this is so, so nice, because as you can see,
04:40 not only are we deriving the type of backend status map and deriving backend status, we're then using that to transform the frontend status there and grab that out. So it means then that going forward, we don't need to edit any of this code. This code is just automatically computing
04:58 the types of frontend status and backend status just by what we add up here. So this is by using as const, we get to do this, and we can also use an index access type to grab all of the values of our object here. So, so nice.