Beginner's TypeScript Tutorial (18 exercises)
solution

Techniques for Typing Dynamic Object Keys

This is another challenge with multiple solutions.

Use the Record Utility Type

One solution is to type cache as a Record:

The first type argument to Record is for the key, and the second is for the value. In our case, both are strings.

The Record type allows us to add any number of dynamic keys to the object at runtime, using something like this:

Record is different than the Set and Map we looked at earlier– it is only at the type level.

Use an Index Signature

Here's another way to update cache to please TypeScript:

Recall that in the "before" code we had lots of errors like "You can't use that to index this".

These errors were saying that they couldn't tell what types the key was. Whenever you see an error about an index, it's usually about an object key!

For this fix, we'll add what is called an index signature to our cache:

Index signatures put the name of the index and its type (string or number) inside of square brackets.

So from the above, we can see that id is the index for cache.

If we were to set it to [id: number]: string, every time we called cache.add() we would have to pass a number as the first argument.

Use an Interface with an Index Signature

We can also create an interface for Cache that contains the index signature:

Then inside of the createCache function we would type cache as Cache:

This would work with a type instead of interface as well.

Which One to Use?

All of these solutions are fine-- there's no pros or cons between them.

I find that the Record syntax is a bit easier to look at and parse what it's doing. It also displays helpful information when you hover over it.

Assigning dynamic keys to an object is a common pattern in JavaScript, and these techniques allow you to do it.

Transcript

This problem has multiple solutions. We've got a type here which I've added to this cache here. What that appears to be doing is it appears to be allowing us to save anything on that cache. We can do anything here. We can say cache dot blah, blah, blah, blah, blah equals something cool here.

What it's allowing us to do is then access things off that cache later, too. We can do cache.cache 23 to equal undefined. This type here, what that's describing is, basically, it allows us to add any number of dynamic keys to that object at runtime. That's what a record type is.

This is different from a set and a map that we saw in the previous exercise, because it's just at the type level. It's just saying that this object here can contain any keys that we want, but they have to be strings. We can't pass numbers here, Booleans, or anything like that. This is one way to express this.

What you might have seen in the exercise here is that you had index types everywhere. It was saying, "You can't index this, can't use that to index this." This is saying basically that if we were to untie this record type, what it's saying is that this here is the index of the objects, the key of the object. Whenever you see index in a type error, it's usually referring to the key of an object.

What this is saying here is that instead of using record string here, we've used this syntax that says the key of this object, we can say index string, and then you get the value afterwards, too. I can rename this to a number if I want to as well.

Inside this cache, everything would have to be cache 1 equals value. That's an alternative way of expressing that. This is what's called an index signature inside a type. Now, we can express this in a third way, too, by using an interface. Interface can use these index signatures, too.

Whichever solution you found, or if you found a solution, record interface or this in-line type here, and I can also extract this out to a type if I want to type blah equals this and then cache is blah. Whichever solution you found -- index string just to make those errors go away -- then it doesn't really matter. There's no pros and cons over either one of these solutions. They're just alternatives.

Usually, record is a little bit easier to look at and easier to pass what it's doing. It also comes with a little descriptor of what it's doing, too. Any of these things can be used to construct different caches. Of course, I can change this second property to anything I want to. I can be like a 1 or a number, etc. I can throw anything in there.

A really common pattern in JavaScript is assigning things to an object with dynamic keys, and this allows you to do it.