TypeScript Fundamentals, v3 Top Types: any & unknown
Transcript from the "Top Types: any & unknown" Lesson
>> The next topic we're gonna discuss is honestly one of my favorite in TypeScript and it's this chapter and the next one. So we're gonna start with top and bottom types, which are types they can hold anything and types they can hold nothing. And then type guards and narrowing.
[00:00:17] So we saw a little bit about type guards already, like type of, instance of, but we're gonna learn how to write our own type guards. So the first thing I want us to talk a little bit about is thinking about types in terms of what is called set theory.
[00:00:39] And this might sound a little bit scary cuz set theory gets very complicated. But I just want you to think of it from the simple idea that we can think about types as defining a set of values that a variable or a function argument might be. So if we were to say that x is of type boolean, what we're really saying is, you may select anything from the following set of values, true or false.
[00:01:16] It can be one of those two things and the set describes all allowed things that x could be. That's as far as we need to go with set theory. So let's think about y being a number. So we could use what's called set builder notation. Hopefully, this is human readable enough.
[00:02:04] So just a couple more examples to sort of hit this point home. Here is a union type that could be one of three specific numbers. So there's the set of things that it could be. Null, actually is its own type, at least with strict null checks turned on, which you should make sure that remains turned on.
[00:02:29] But that's one thing that that could be. And here's an optional variable or an optional property on some object type that could be exactly the string pineapple or it could be undefined. Hopefully making sense so far. So type just describes a set of things that you could find in the variable that type describes.
[00:03:58] Obviously, there are risks around using any. This is clearly at runtime going to be a number, but I'm reaching into it as if it's got many, many nested things and they're not going to be there. This will result in a runtime error. Now I got this question earlier, is it wrong to use any?
[00:04:18] Is there any time where it's the appropriate thing to do? And my answer is yes, there are times when it's absolutely appropriate. A good example is console.log. If we look at the type of console.log, it has a rest parameter here. So it takes any number of arguments you like, each of which is an any.
[00:04:41] This is appropriate because console.log can log just about anything to the console. There's no reason to impose additional constraints here. Any describes accurately what you should be doing here. So it's the absolute right thing to use. This is not something that should be tightened up. This is correct.
[00:05:00] All right, let's look at unknown. Just as with any, you can see that I can put a number in it, a string, document, a function like set timeout, seems a lot like any. But here's the key difference. Unknown can't be used unless you narrow it, right? Unless you use a type guard with it to check it out, to make sure that it's acceptable for use.
[00:05:33] So it's almost like it comes with a warning label that says, you must verify that this is what you think it is before you go ahead and do something with it. So in this case, you can see, yeah, we can put any value into it that we like, but we can't reach in and start accessing it directly.
[00:05:53] We must do something like this. So you see my unknown, right at the time that we're checking it before we established that it could be a string, it's an unknown. And then down here, it's a string. So we're free to use it as a string. And then here's another condition where it's an unknown.
[00:06:14] But as soon as we make it into this code block, hey, it's a number, and we're free to use it as a number. So it's as flexible as any in terms of its ability to hold things, but it places an extra responsibility on whoever uses this value to kind of check it out first with the type guard.
[00:07:09] To make a very specific well articulated constraints with types and just try to convert the whole thing in one big code triage, that's dangerous, right? Your odds are you're gonna break something cuz it's just a lot of potential to change behavior while you're doing that. It's common to define some reasonable types where it's easy, where it makes sense, and to leave a lot of anys in there, and to sort of clean those up in successive passes as you sort of tighten things up, tighten things up.
[00:07:42] So any is really useful for sort of work in progress mid conversion states. And it's also useful where you frankly just don't have any idea what's going on. Like you have no compile time ability to verify things. An example would be you just received a fetch response and you decoded it from JSON.
[00:08:06] And you might think you know what it is based on the endpoint, but can you really be sure? What if that API goes down and it starts sending you error messages? You might wanna verify that. That might be a great use for unknown, values received at runtime from your data layer.
[00:08:24] This works really well with discriminated unions by the way. Remember where we had that tuple where there was the success and there was the error. So maybe you receive an API response and on every API response, there's a little key where if you see that property there, that's a strong signal that you know what the rest of the object looks like.
[00:08:43] So you can receive it as an unknown, check for the presence of the key. Okay, now we know what it is and you have a nice type for that thing. It's a great thing to use for data layers.