Alex's Notes

Typescript Basic Type Checking

As presented in Typescript: UI.dev

Type Inference

Typescript’s type system is designed to be easy to pick up. Part of this is its type inference system, which means explicit type annotations aren’t strictly required.

If you declare let myVar = "a string" and then try to assign another type to myVar typescript will warn you.

Type inference works for all primitive types.

It also works for object literals. If you define something like:

let fruit = {
  name: "banana",
  color: "yellow",
  sweetness: 90,
  ripe: true
}

you’ll see in the IDE that TS has inferred the type for each value in the object. If you then destructure or otherwise pull out the properties, TS would infer the types of variables too.

If you have an array literal, it will also infer the type of the array. If you have an array of strings it will infer string[] as the type, or mixed arrays will be eg (number | string)[].

It can infer types from the context of creation. EG in a map function it will infer based on the array you’re iterating over, and work out the return result too.

But if we pulled out the function from the map and declared it elsewhere, then TS couldn’t make the same type inferences, since it doesn’t know where else we’ll be using that function. It will fall back to the any type, which depending on our config could throw errors if we have no implicit any set.

We can use explict type declarations to get around this.

Type Annotations

Type inference is powerful but there are times when TS can’t always infer the type. EG if we’re not assigning the value when we declare a variable.

It will fall back to any. This is where we can use explicit annotations to help specify the type of the variable. For example function parameters.

To add a type annotation we put a colon after the variable name and then the type.

For example: let fruitCount: string.

Note that type annotations are lower case since JS has its own constructors for its primitive types.

Note also that so far as JS is concerned, a declared variable that hasn’t been assigned is undefined. Even if you declare its type to be something else in TS. TS will, accordingly, warn you if you try to use a variable before assignment.

But there might be situations where we want to use a variable even if we haven’t assigned it. We can use a non null assertion to tell TS that we don’t care if it’s used before it’s assigned.

We do this by putting an exclamation before the colon. Like this: let fruitCount!: number. Then TS won’t warn you about using it before assignment.

Array types can be declared with the array brackets syntax: let ingredients: string[].

There is another syntax for this: let ingredients: Array<String> which is valid, but not commonly used. it’s not compatible with JSX syntax so better not to use it.

We can define object types with curly brackets and then the types of the properties:

let menu: {
  courses: number;
  veganOption: boolean;
  drinkChoices: string[];
}
END_SRC

One useful thing is to place a variable of any type to a variable of a defined type. For example in network requests.

If you have something like ~const fruitlist = await response.json()~ TS can't know the type of fruitlist, it will be ~any~. It's not going to know at build time what the response will look like.

So we tell typescript like this: ~const fruitlist: string[] = await response.json()~. Note that we might here still run into runtime type errors here - the network response might be an object, not an array. TS won't know about this - it's a build time tool. So we need to be careful ourselves with this type of runtime issue.

We can annotate function paramter types too.

Let's imagine we have a function that can take a string or an array and return its length. I want TS to warn me if I pass an object with a length property instead.

I could define it like this:

~function stringOrArrayLength(input: string | unknown[]) { return input.length }~

What if my function is more complicated?

We can overload function declarations to define the possible inputs like this:

#+BEGIN_SRC ts
function stringOrArrayLength(input: string): number;
function stringOrArrayLength(input: unknown[]): number;
function stringOrArrayLength(input: any) {return input.length}

Note that we still need a type annotation on the actual function definition, but we’ll still get TS warnings if the use of the function doesn’t meet the overloaded declarations.

We can also add a type annotation to the this keyword on an instance method in a class if we want to help TS understand the instance object type.

Links to this note