DEV Community

Cover image for Prefer strict types in Typescript
Serhii
Serhii

Posted on

Prefer strict types in Typescript

Types should be as close to reality as possible. Otherwise, they mislead (reduce truthiness in the system) and, thus, slow down development.

Usually, it's self-evident in many statically typed languages, but it's a different case with Typescript since it allows more types flexibility.

any

any may be a favorite type for many developers who migrated from Javascript recently. It declares that a type could be anything. There are some use cases for using any, but usually it's recommended to not employ it. One such example I could think of is silencing the Typescript compiler to run a script quickly. Otherwise, it detracts truth from a system and eliminates the benefits of using Typescript.

any type in typescript visualized

If you don't know what your data looks like, how are you going to handle it properly? Without proper handling, a system (or part of it) fails.

The other disadvantage of using any is silencing any potential errors you make when handling such data, which leads to another system failure.

const anything: any = 'word';
const result = anything * 5;
Enter fullscreen mode Exit fullscreen mode

If you remove any declaration from the anything constant, Typescript tells you what is wrong exactly.

What if I don't know the exact type of data? Imagine I retrieve it from a third-party API that may change tomorrow. Or, from a non-typed npm package because it's written in Javascript. This is where we use unknown.

unknown

The useful unknown type states that a variable shape is not known and can be anything, the same point as any declares. The only difference is that unknown requires the compiler to check a type first before operating on data.

const anything: unknown = 5;
const result = typeof anything === 'number' ?
    anything * 5 :
    undefined;
Enter fullscreen mode Exit fullscreen mode

The primary use case for unknown is to mark variables that are unknown and can be anything, so you must validate a type first before manipulating data.

unknown type visualized

If data shape is known, it's always better to specify an accurate type.

Narrowing

Narrowing is a process of giving types more accurate shapes. I.e., bringing them to reality, so they reflect a real data form.

To illustrate the motivation behind it, take a look at this code:

const record: Record<string, number> = {
    field: 2,
};
const result = record.field2 * record.field3; // NaN
Enter fullscreen mode Exit fullscreen mode

Yes, record, is Record<string, number>, but it's not accurate enough. Record means an object with who knows how many fields and their values are of type number. In this case, a more accurate type is { field: number; }. In this example, you can skip declaring a type because the compiler can infer it automatically.

Another example of bad narrowing:

const fn: Function = (arg: number) => {
    return arg * arg;
};

fn('string'); // NaN
Enter fullscreen mode Exit fullscreen mode

Yes, fn is a function, but this type doesn't specify the details (and as in the case above, you should skip declaring a type explicitly because the compiler will infer it for you automatically).

Common types to narrow

For these examples, T can be any type.

  1. Record<string, T>: prefer an accurate object shape, if you know it. E.g.,
type MyObject = { name: string; }
Enter fullscreen mode Exit fullscreen mode
  1. T | null | undefined means you need to handle both null and undefined cases besides the presence of value. Prefer T | null, T | undefined, or merely T.
  2. Function: prefer an exact function signature, e.g.:
type MyFunction = (arg1: number, arg2: string): string;
Enter fullscreen mode Exit fullscreen mode
  1. Partial<T>: prefer a more accurate type.

Originally published on absolyd.com

Top comments (0)