Using any in TypeScript gives us a false sense of safety

Β· 3 mins read Β· Β JavaScriptΒ TypeScript

TypeScript is a strict syntactical superset of JavaScript that adds optional static typing to it. In a few words, it makes JavaScript hell stronger and safer language.

The web needs TypeScript since we tend to build bigger and more complex applications with JavaScript but the language itself has some well-known weaknesses. This is where TypeScript comes into the picture to fill some blanks.

# Why TypeScript?

Let's see a dead simple example then:

// With plain JavaScript
const sum = (a, b) => a + b;

// With TypeScript
const sum = (a: number, b: number) => a + b;

const c = sum(2, 3);
// c === 5

So we created an arrow function named sum and we declared its arguments as numbers. That way we are certain that the outcome will always be a number which is what we want actually.

Let's say we accidentally pass a string that looks like number to the eye, but it definitely isn't an actual number for JavaScript. What will be the outcome for each case?

// With plain JavaScript
const c = sum('2', 3);
// c === 23

// With TypeScript
const c = sum('2', 3);
// Argument of type '"2"' is not assignable to parameter of type 'number'.ts(2345)

As we saw JavaScript kept on working by simply concatenating the two arguments while TypeScript raised an error by warning us that we passed a faulty argument that is not a number.

This is extremely beneficial since we are in position to spot such an error during development and tackle it early enough. The worse thing is that JavaScript kept on working even with a faulty argument, so we could easily let this slip through in our application if we don't pay attention or even worse if we haven't established a robust suite of tests.

# Why any?

There are times though that we get lazy or we are not in position to declare a type accurately. Don't get mistaken, the example above was trivial. The more complex applications we are building the more challenging it becomes sometimes to declare accurately an interface or a type.

This is where any comes into the game 😈

If you have used TypeScript I am sure you can recall a bunch of times during the early days that you wanted even to cry. It happens, I know and you shouldn't let it get into your head. You can win this battle πŸ’ͺ

Let's refactor the example above by applying any type:

const sum = (a: any, b: any) => a + b;

We replaced number type with any so we can now accept any possible type in there.

That said, if we pass the same pair of arguments we did before, these are the results:

const c = sum(2, 3);
// c === 5

const c = sum('2', 3);
// c === 23

Arghhh, so we did pass a string in the second example and we ended up with a bloody concatenation same as we did before with plain JavaScript. Ok, but it is TypeScript, right? We are safer and we will fix it in the future. No?

No you won't fix it and this is definitely not safe. There is no point in using TypeScript if you force it to behave like plain JavaScript.

Sorry, this is bad and will harm your project sooner or later πŸ€·πŸ»β€β™‚

# Can we protect ourselves?

The truth is that we might need to use any especially while migrating a JavaScript application to TypeScript. For all other cases we need to avoid using any type in order to enjoy type safety.

TypeScript has predicted this by providing a flag variable we can use in its tsconfig.json configuration file named noImplicitAny. So if this is equal to true then TypeScript will start screaming every time it spots any type.

This configuration option can prevent us from doing silly mistakes by forgetting having any types here and there .

{
  ...
  "noImplicitAny":  true,
  ...
}

# Can we still override this?

We can still force TypeScript to ignore an error even with noImplicitAny enabled, by using ts-ignore. This will suppress all errors for the very next line only.

Below we haven't declared a type for user argument, so we will get a warning:

const greeting = user => {
  const { name } = user;

  return `Hello ${name}`;
};
// Parameter 'user' implicitly has an 'any' type.ts(7006)

We can suppress this warning with ts-ignore:

// @ts-ignore
const greeting = user => {
  const { name } = user;

  return `Hello ${name}`;
};

Obviously this is a bad practice, so we need to use it sparingly.

In the official docs we are advised against this too:

Please note that this comment only suppresses the error reporting, and we recommend you use this comments very sparingly

# Is there an alternative?

If we really don't know what is the actual interface we need to declare - let's say for an API response - we can still avoid using any by taking advantage of unknown type.

This was introduced in TypeScript 3 and as it is declared in the official docs:

unknown is the type-safe counterpart of any. Anything is assignable to unknown, but unknown isn’t assignable to anything but itself and any without a type assertion or a control flow based narrowing. Likewise, no operations are permitted on an unknown without first asserting or narrowing to a more specific type

This means that we can use unknown and run some logic, but we cannot do unsafe operations.

This will work:

const greeting = (name: unknown) => `Hello ${name}`;

greeting(1);
// Hello 1

greeting('John Doe');
// Hello John Doe

But the following will throw an error since we don't actually know more details about user argument so it isn't safe to destructure it:

const greeting = (user: unknown) => {
  const { name } = user;

  return `Hello ${name}`;
}
// Property 'name' does not exist on type 'unknown'. ts(2339)

That way we manage to stay type-safe even when we are in a tricky situation, and we are tempted to use any. Cheers!!

You can read even more regarding unknown type in the official docs here

Newsletter

Get notified about latest posts and updates once a week!!