Use the TypeScript "unknown" type to avoid runtime errors

Rares Matei
InstructorRares Matei

Share this video with your friends

Send Tweet
Published 4 years ago
Updated 4 years ago

The "any" type can be very useful, especially when adding types to an existing JavaScript codebase, but it can also lead to lots of runtime errors, as it provides no type-checking. The "unknown" type on the other hand, restricts developer from calling anything on it or assigning to any other type. The only way to use "unknown" variables is to apply logic flow type narrowing or casting, as that's the only way TypeScript can trust that it's dealing with a correct type. The "unknown" type is useful when building services or APIs that can return data of an unknown shape, so we might want to stop other developers from using it in potentially dangerous ways until it's been narrowed down to a specific type.

Instructor: [00:00] To demonstrate the any type, I created here a simple array of strings. If I try to call methods on it, I'm going to get valid array method suggestions because Typescript correctly inferred this variable's type based on what was assigned to it.

[00:12] If I also write a function that only works with array of strings, Typescript will let me call that function and pass in my array.

[00:24] Of course, if I try to access a bunch of random properties on it, it's not going to work. I obviously also can't invoke an array because it's not a function. Both of these are going to fail.

[00:38] Now, if I explicitly change the type of this variable to an any, all of the errors will go away. I can assign anything to an any. You can also assign an any to anything. That's why it lets me call this function here.

[00:50] Finally, you can access any property on an any. You can even invoke it. Anything goes. Any is the most relaxed type in Typescript, as you can see. There are perfectly valid use cases for it.

[01:01] Let's say here we have some vanilla JavaScript code base. We suddenly decide that we want to add Typescript to it. We know this code works fine and this function works fine as it is. We just don't have a specific type for its parameter yet.

[01:17] We can just set it to any temporarily. Sometime later, once we've had some more time to define more proper types in our application, we can add it to our function's definition.

[01:29] It can also get you in a lot of problems. Both of these statements here will throw errors at runtime. Typescript is not saying a word about that. I can mess this up even more. Instead of an array here, just assign a Boolean to it, which makes all of this even worse as everything in here will throw errors now. Typescript is still fine with all of it.

[01:47] The any type will negate any protection you get from Typescript on the variables it's used on. What if I had a service that queries some third party API? That data can change. We are definitely not responsible for it and we can't log in the type for it.

[02:02] In that case, we'd need to return the response as an any until we can do some proper checks around it, and only then attempt to call any methods on it.

[02:14] In this case, we're sure that if it gets to run inside this block of code, it's definitely a string. As it is, this code should work and we are protected.

[02:25] Between the time this service returns and we do the check, another developer that might not be aware of the constraints can still come in and do anything on the return value without Typescript complaining. Now, we're back to having runtime errors.

[02:41] This is where the unknown type shines. If instead of an any here, I return an unknown, I am telling Typescript and the developer that it's not really safe to use this variable. Suddenly, this will start throwing errors, which is great, but how is this useful if we can't use it?

[02:58] You'll notice that in this block of code, it's not throwing any errors. It's letting us call string methods on it, which is because the unknown type stays unknown until you do some type narrowing on it. In this case, Typescript will be sure that it's a string.

[03:14] I can also go ahead and build a user-defined type guard, and then just open up another conditional block and use it. Again, even though initially, response was unknown in this block because of the type guard, it now knows it's a comment. It's going to let me access comment properties on it.

[03:33] Finally, if you're sure about its shape, you can even cast it. The unknown type is much safer for cases like this.

[03:44] To recap, while both any and unknown might on the surface both signal the intent that we don't yet care about the type of a variable, they are very different.

[03:55] With any being the least restrictive type, allowing you to call anything on it and assign it to any other type, unknown is the most restrictive and doesn't let you call anything on it. You can't assign it to any other type until you narrow it down to a more specific type via control flow type narrowing or casting.