Do you really know TypeScript? (2): Being strict
Tags: javascript, programming, typescript, node
Published: 2021-12-27 14:29:10

## 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.

```typescript
function greet(name) {
    return `Hello, ${name}`
}
```

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.

```typescript
 const jame: Person = null

 // It'll throw "cannot read 'greet' of undefined" at runtime
 jame.greet()
```

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.

```typescript
// No errors even with strict: true
const age: number = "4" as any
const name: any = 3.1416
```

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:

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

const result = db.query<User[]>('select * from user')
``` 

### 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:**

```typescript

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}`);
  }
}
```

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
- [Effective TypeScript](https://www.goodreads.com/en/book/show/48570456-effective-typescript)
- [TypeScript docs](https://www.typescriptlang.org/docs/)