It can be tempting when using TypeScript to use any
when you are unsure what type a variable is. This article explains why this could be a bad idea and presents some ways to avoid using it.
What is any?
- The TypeScript docs define
any
as “a type of variable that we do not know when writing an application”. any
is typically used when interacting with values that come from dynamic content e.g. user input, or a third party libraryany
is a way to bypass the compile time checks that TypeScript will complete when you invoke thetsc
command to turn your TypeScript into JavaScript that’s runnable in the browser - usually done as part of a build process.
Why should I care?
Using any
can lead to runtime bugs that would’ve been simple to stop at compile time.
Everything may look good and your code will “compile” but the use of any
will shift the problems from compile time to runtime, and more importantly, will shift the problem from us as a developer to the end user i.e. our customers.
It is better to spend an extra few minutes defining the type for a variable than the support team spending a day cleaning up data because a request failed half-way through because it tried to access a non-existent property on an object in JavaScript.
Take the example below:
type SomeFunctionInput = { test: string };
const someFunction = (input: SomeFunctionInput) => {
// problem: someFunction accepts the input
// which we've told it is an object that has the property test...
// So... I know my input must have the 'test' property... right?
console.log("let's try calling input.test", input.test);
};
const myFunction = (input: any) => {
// problem: we can just reassign input to any type we want...
input = undefined;
// second problem: someFunction accepts undefined as an input
// since it's technically `any`
someFunction(input);
};
console.log("this should log 'hello'", myFunction({ test: "hello" }));
What actually happens here is a runtime error which the user will see and that will halt execution in the browser - specifically “Cannot read property ‘test’ of undefined” - not a compile time error, which is the whole point of using TypeScript.
Using TypeScript correctly could have prevented this. First of all if we assign input to a real type in the myFunction
declaration then we get a compiler error when we try and assign undefined to the variable “input”.
Let’s say we changed it to
(input: object)
(which is still pretty bad - as it’s not very explicit)
but, our error would now be:
[typescript] Type 'undefined' is not assignable to type 'object'
Even if we used a type that can be assigned undefined, e.g. unknown (more on this type later), we still get an error when we try and pass this to someFunction
:
[typescript] Argument of type 'unknown' is not assignable to parameter of type 'SomeFunctionInput'.
Sure, this is a contrived and trivial example, but it highlights the problems that using any
liberally may introduce to your code.
It would be more beneficial to pay the upfront cost of being strict with typing when writing the code initially, to eliminate a whole class of bugs that a language like JavaScript may allow us to introduce.
What can we do about it?
We can take care when writing code and spend a bit of time trying to figure out what type something really is, rather than defaulting it to any
. Otherwise use unknown
in place of where you would have normally used any
.
Tips to help avoid using any in your code:
Use unknown for values that can be anything
- Any value can be assigned to
unknown
** however, it doesn’t let you do anything with that value until we know the type (through proper type checks). - This is useful for functions where you want to accept any type but want to enforce runtime type guards e.g. a pretty print function
Example:
const print = (input: unknown): string => {
if (typeof x === "string") {
return `"${x}"`
}
if (typeof x === "number") {
return String(x)
}
// ...
Use Record when you’re working with something like to a dictionary:
Example:
const dict: Record<string, number> = {};
dict.a = 1;
dict.b = "a"; // TypeScript error: Type 'a' is not assignable to type 'number'.
// Or maybe we're not sure what our values will be?
const dict: Record<string, unknown> = {};
dict.a = 1;
dict.b = "a"; // Works fine! Note that we still didn't have to use `any`
Conclusion
To conclude, any
can be used when you want to start gradually adopting TypeScript in a really complicated project, or if you’re accepting constantly changing third-party data - but think carefully before using it in your project.
If you are unsure what type a certain variable is, try using unknown
and fall back to runtime type assertions before doing anything with the variable.
Using proper types will enforce compile time checks for your code, ensuring that the errors that are often prevalent in JavaScript won’t happen at runtime and affect the end-user.
It also integrates nicely with every IDE and allows for much better code completion since the IDE knows the type of every variable you’re working with.
If you’re struggling to generate types for certain objects in your code, create an instance of the object, log it, and use something like quicktype.io to generate the TypeScript for you! (There’s also a VSCode plugin )