If you’ve been using TypeScript for a while, you’re probably familiar with the “any” type. This type is often used as a placeholder for a value whose type is unknown or difficult to specify. While using “any” can make your code more flexible, it also comes with a number of drawbacks. For example, it can make your code less type-safe and more prone to runtime errors.
Fortunately, TypeScript provides a number of alternatives to “any” that can help you write more robust and maintainable code. In this article, we’ll take a look at some of these alternatives and explore how you can use them in your TypeScript projects. Whether you’re a seasoned TypeScript developer or just getting started, read on to learn more about what to use instead of “any”!
What Is “any”?
For a more in-depth introduction, you can check out this article. To sum it up, the “any” type is a special type in TypeScript that can be used to represent any type of value. When you use “any” in your code, you’re essentially telling TypeScript to disable type-checking for that value. This means that the value can be of any type, and TypeScript won’t perform any type checks on it.
The fact that “any” does not provide any type-checking is a benefit and a disadvantage. While “any” can be useful in some situations, it can also lead to problems in your code. Since TypeScript doesn’t perform any type checks on values with the “any” type, it can be difficult to catch errors at compile time. This can result in runtime errors that can be hard to debug, especially in larger codebases.
Unknown
The type “unknown” is very similar to “any”, in that it represents something you don’t know the type of. The key difference is while “any” will let you do anything to a type, “unknown” won’t let you do anything until you check what type your variable is.
For example, let’s take a look at this function:
function parseJSON(jsonString: string): any {
return JSON.parse(jsonString);
}
Here’s an issue you might run into a lot. JSON.parse has no way of knowing the type of the JSON you’re parsing at compile time. Since its type is any, you can do something like this:
const JSONFile = '{"foo":"bar"}';
const myJSON = parseJSON(JSONFile);
console.log(myJSON.foo); //'bar'
This code works fine, but let’s be a little stricter.
Firstly we’ll swap out our any for an unknown:
function parseJSON(jsonString: string): unknown {
return JSON.parse(jsonString);
}
Which now causes our code to error:
console.log(myJSON.foo); //TS18046: 'myJSON' is of type 'unknown'.
So firstly we need to create a type for our JSON:
type Foo = {
foo: string;
};
We then need some way of telling TypeScript if our JSON is a Foo. We can do this with a type guard:
function isFoo(obj: unknown): obj is Foo {
if (!obj) return false;
return typeof obj === 'object' && Object.hasOwn(obj, 'foo');
}
Here I’m simply checking that our object exists, checking it is an object, and then checking it has the property we want. Then, we can change our code to:
if (isFoo(myJSON)) console.log(myJSON.foo);
And the error is fixed! While this way is more verbose, the extra type checking we’ve added gives us, and TypeScript more confidence in our code.
Typing
With unknown, we’re checking if our variable is a type before we’re treating it as that type. If you’re more confident that it will be that type, however, it can be easier to just directly swap “any” for the type.
Let’s change our function to be generic:
function parseJSON<T>(jsonString: string): T {
return JSON.parse(jsonString);
}
We can then provide the type when we call the function:
const JSONFile = '{"foo":"bar"}';
const myJSON = parseJSON<Foo>(JSONFile);
console.log(myJSON.foo);
Conclusion
Which option you use may depend on your situation. The “unknown” type is less certain, you have to check if your variable is that type before you can treat it as one. This gives you more confidence; as long as the type guard function is accurate, there’s no way for things to go wrong.
Directly swapping the type is a quicker solution, and might be useful in examples where you know the type is going to be right. In our example, you might use the first when loading user input, just in case a malicious user tries to load some JSON that doesn’t match the type you’re expecting.
For the second, you might be loading some JSON data for config as part of a smaller project. If you know you’re writing the JSON, you might be able to avoid skipping the type guard and just assume you’ve written your config file correctly.
In any case, hopefully, now you have the knowledge to be able to reduce the use of “any” in your codebase. If you liked this article, feel free to leave a comment below!
💬 Leave a comment