TypeScript – How to avoid “any”?

0

0

26/09/2022

  • The harmful effects of any
  • Avoiding any

How to avoid any?

In the previous blog – Typescript and “any” type, I introduced TypeScript and what exactly is any type.

In this blog, I’d like to show more about the harmful effects when using any and introduce some built-in types, features and customs types that you can use to avoid any.

The harmful effects of any

While TypeScript is a type checker, any type tells TypeScript to skip/disable type-checking. On the other hand, due to the nature of JavaScript, in some cases providing accurate types isn’t a simple task. In such situations, programmers are tempted to use any.

In most situations using or implicit any – a type that allows to store anything and skip type checkers, programmers can’t guarantee what type the value is stored and how it will be used. Furthermore, when the code is executed at runtime, errors may occur even though they were not warned before. For example:

let result; // Variable 'result' implicitly has an 'any' type.
result = 10.123; // Number is stored at 'result'

console.log(result.toFixed()); // `toFixed()` is a method of `number`

result.willExist(); // `willExist()` isn't a method of `number`, but no errors appear.

Because of that, the use of any is something that should be minimized as much as possible, to ensure the source code does not encounter any errors.

Avoiding any

Based on the basics of TypeScript and Everyday Types, in this blog, I’ll be sharing what I learned and used to write code without any.

Type aliases & Interfaces

A type alias is exactly a name for any type, you can actually use a type alias to give a name to any type at all, not just an object type. For example:

// Type alias
type Point = {
  x: number,
  y: number
};

type ID = number | string;

An interface declaration is another way to name an object type:

// Interface
interface IPoint {
  x: number,
  y: number
};

Differences between Type Aliases and Interfaces:

// Override
type Point = { // TypeError: Duplicate identifier 'Point'.
  a: string
};
interface IPoint {
  a: string
};

Union & Literal types

A union type is a type formed from two or more other types, representing values that may be any one of those types.

// Union types
let anyNumber: string | number;

// Usage
anyNumber = '123';
anyNumber = 123;
anyNumber = true; // TypeError: Type 'boolean' is not assignable to type 'string | number'.

In addition to the general types of string and number, you can refer to specific value of strings and numbers.
By combining literals into unions, you can express a much more useful concept. For example:

// Literal types
let direction: 'top' | 'left' | 'right' | 'bottom';

direction = 'top';
direction = 'top-right'; // TypeError: Type '"top-right"' is not assignable to type '"top" | "left" | "right" | "bottom"'

Type assertions

Sometimes you will have information about the type of a value that TypeScript can’t know about.

For example, if you’re using document.getElementById, TypeScript only knows that this will return some kind of HTMLElement, but you might know that your page will always have an HTMLCanvasElement with a given ID.

In this situation, you can use a type assertion to specify a more specific type:

// Type assertions
const myCanvas = document.getElementById('main-canvas') as HTMLCanvasElement;

Generics

// Example
const getRandomNumber = (items: number[]): number => {
  let randomIndex = Math.floor(Math.random() * items.length);
  return items[randomIndex];
};
const getRandomString = (items: string[]): string => {
  let randomIndex = Math.floor(Math.random() * items.length);
  return items[randomIndex];
};

// Generics function
const getRandomGeneric = <T>(items: T[]): T => {
  let randomIndex = Math.floor(Math.random() * items.length);
  return items[randomIndex];
};

// Usage
const teams: string[] = ['frontend', 'ios', 'android'];
const numbers: number[] = [1, 2, 3, 4, 5, 6, 7, 9, 10];

const randomResult1 = getRandomGeneric<string>(teams);
const randomResult2 = getRandomGeneric<number>(numbers);

In the example above, the getRandomGeneric is the generic identity function that worked over a range of types.

The type of generic functions is just like those of non-generic functions, with the type parameters listed first, similarly to function declarations:

const identity = <Type>(param: Type): Type => {
  return param;
};

When calling identity a function, you now will also need to specify the type of param that the function will use.

The detail above just Generic identity functions, you can read more generics here

Unknown

unknown is what should be used when you don’t know a proper type of object. Unlike any, it doesn’t let you do any operations on a value until you know its type (skip/disable type-checker).

When you unknow something, you need to check before executing. For example:

const invokeAnything = (callback: unknown): void => {
  if (typeof callback === 'function') {
    callback();
  }
  if (typeof callback === 'number') {
    console.log(callback);
  }
  if (typeof callback === 'string') {
    console.log(callback.toUpperCase());
  }
};

// Usage
invokeAnything('typescript'); // Result: TYPESCRIPT

Record for basic object

Probably, nearly every JavaScript developer at some time has used an object as a map-like collection. However, with strict types, it may not be that obvious how to type this. So, you may use interface, but this way you can’t add anything to the object. Then, you need to think about using Record.

The definition:

type Record<K extends keyof any, T> = {
  [P in K]: T;
};

And the usage:

// Usage
const dict: Record<string, number> = {};
dict.a = 1;
dict.b = 'a'; // TypeError: "a" is not assignable to type number

let obj: Record<string, number>;
obj = {
  a: 1,
  b: 2
};

As you can see, it means that the developer can enter any key, but the value has to be of a specific type.

Conclusion

The TypeScript compiler is so powerful. There are so many things we can do with it.

any type can be avoided with more advanced technics such as interface, type intersection, and the use of generics, etc.

Hope you like it! Enjoy TypeScript and make the code without any!

Author: Anh Nguyen