DEV Community

Cover image for Do you really know TypeScript? (2): Being strict
Alex Menor
Alex Menor

Posted on • Edited on

Do you really know TypeScript? (2): Being strict

Strict settings

The TypeScript transpiler has an overwhelming set of options 🥵, but don't worry, you don't have to know them all.

Though, you should know these two really well:

  • noImplicitAny
  • strictNullChecks

When noImplicitAny is enabled all variables must have a known type.

function greet(name) {
    return `Hello, ${name}`
}
Enter fullscreen mode Exit fullscreen mode

If you hover over the function (and your IDE has TypeScript capabilities) you will see that it infers that name is of type any.

We can say that name is implicitly of type any, and if noImplicitAny is disabled TypeScript will rightfully complain 🙂

As we'll see more in depth later, any bypasses TypeScript's type checks, making values of the any type assignable to anything.

Using the any type should generally be your last resort and if you really need to use it, you have to do so explicitly if noImplicitAny is enabled.

Although noImplicitAny enables you to make the most out of TypeScript, it can be tough to have this setting enabled if you are migrating your codebase from JavaScript, for example.

As we already mentioned, you can see types in TypeScript as sets of values.

strictNullChecks controls if null and undefined are part of every type.

 const jame: Person = null

 // It'll throw "cannot read 'greet' of undefined" at runtime
 jame.greet()
Enter fullscreen mode Exit fullscreen mode

This code is going to throw an error when you execute it.
But, with strictNullChecks enabled, TypeScript will tell you at compile time instead:
Type 'null' is not assignable to type 'Person'.

There are more "strict" settings that modulate how picky TypeScript is and you can turn them all on with strict: true.
I would advise you to do so, specially if you are starting a project from scratch.

Handling type edge cases

Before introducing the empty and universal sets as promised, we have to talk about any, which is often perceived as the universal set.

What should I use any for, then?

TypeScript is a gradual type system, you can type some parts of your code and leave others untyped. any enables that, disabling the type checks.

  • You can assign a value of the any type to anything
  • You can assign anything to a variable of the any type

any doesn't fit in the "type as a set of values" model, since a set cannot be a subset and a superset of everything at the same time.

// No errors even with strict: true
const age: number = "4" as any
const name: any = 3.1416
Enter fullscreen mode Exit fullscreen mode

Be specially cautious when it comes to using any as a return type as it can spread to other well typed parts of your code that make use of said function.

The universal set

Important points of unknown:

  • Any type is assignable to unknown because every type is a subset of it.
  • But unknown is not assignable to any type but itself (or any) because it is not the subset of any other type.
  • Attempting to access a property on a value of the type unknown is an error.

The last point is key, specially when using it as an alternative to any for edge cases when we really don't know the return type of a function, for example.
When using unknown, the untyped code doesn't spread as we need to narrow the types in it in order to use it.

Besides narrowing it with an assertion, some libraries use generics for this:

function query<T>(q: string): T;

const result = db.query<User[]>('select * from user')
Enter fullscreen mode Exit fullscreen mode

The empty set

The never type is the opposite of unknown:

  • Nothing is assignable to never because no set is a subset of the empty set.
  • never is assignable to everything, because the empty set is the subset of every set.

The use of never is not as frequent as unknown but it does have an use case that I like a lot called exhaustive type checking:


type SpanishChampionsWinners = 'Real Madrid' | 'Barcelona'


function getChampionsCount(team: SpanishChampionsWinners): number {
  switch (team) {
    case 'Real Madrid':
      return 14;
    case 'Barcelona':
      return 5;
    default:
      const exhaustiveCheck: never = team;
      throw new Error(`We forgot: ${team}`);
  }
}
Enter fullscreen mode Exit fullscreen mode

If one day "Atlético de Madrid" wins a Champions title, adding it to the SpanishChampionsWinners type will make this code complain since no value is assignable to never.

Things to remember:

  • Be as strict as possible with your TypeScript settings and know noImplicitAny and strictNullChecks well.
  • Understand that any does not fit in the "types as sets" model, being a mechanism to avoid types in parts of your code.
  • Try to isolate the untyped parts of your code and be aware of the any spreading.
  • Understand why unknown is preferable to any when handling edge cases.
  • Get the idea of never and use it for exhaustive checking.

Resources to go deeper

Top comments (0)