Types

There is a standard Date type built into Typescript. You can construct a Date object using the new keyword.

let now: Date = new Date();

Aliases

You can define an object type using a type alias. The fields can seperated by , comma or a ; semi-colon.

type Person = {
  name: string,
  age: number,
}

You can define optional object properties using a ? question mark after the property name.

type Person = {
  name: string,
  age?: number,
}

If a value is not provided by a property defined as optional, then that property will be marked as undefined. This is useful because it means a runtime error will not be triggered if the property is accessed.

You can alias union types.

type Id = number | string;

You can extend an existing type alias by creating a new type using an intersection. This will inherit properties from the existing type and allow you to add new properties.

type Dog = {
  name: string,
  woofsPerSecond: number,
}

type Robodog = Dog & { // type intersection here
  operatingSystem: string,
}

const buzzo: Robodog = {
  name: "Buzzo",
  woofsPerSecond: 9000,
  operatingSystem: "woofSX",
}

Interfaces

You can use interfaces to define object types.

interface Person {
  name: string,
  age: number
}

You can add properties to an interface by redefining it. It seems like this might also limit you from totally redefining an interface.

interface Person {
  name: string,
}

interface Person {
  age: numnber,
}

const person: Person = {
  name: "Mike",
  age: 444,
}

You can extend an interface using the extend keyword to create a new interface that inherits the properties of another interface and adds new properties.

interface Dog {
  name: string,
  woofsPerSecond: number,
}

interface Robodog extends Dog {
  operatingSystem: string,
}

const buzzo: Robodog = {
  name: "Buzzo",
  woofsPerSecond: 9000,
  operatingSystem: "woofSX",
}

Aliases vs Interfaces

Generally aliases and interfaces can be used interchangeably. However there are some key distinctions.

Aliases are static and cannot be redefined.

type Animal = {
  name: string
}

// Error: Duplicate identifier 'Animal'.
type Animal = {
  petName: string
}

Interfaces cannot be used to alias primitives.

Type Assertions

If a particular complex type assertion is too constrained by the compiler but is known to be correct it can be solved by chaining an assertion an any type and then to a target type.

const x = (result as any) as T;

Literals

Literal values use type hints which constrain the possible values that a type can take. For example you could constrain a variable to a single string.

let x: "hello" = "hello";
x = "bonjour" // ERROR: "hello" is not assignable to "bonjour"

This is also how const values work but assinging a static type with a specific value. The above let is equivalent to assigning x to a const of "hello".

You can constrain values using literal type unions so that only a specific set of values can be used.

type direction = "north" | "south" | "east" | "west";
let dir: direction = "north";
dir: direction = "up" // ERROR not assignable to type direction

Literals are not automatically inferred when defined inside objects. The properties val and name of the object below are inferred to be simply string and number and so can be mutated. They are not literals despite what defining a const object with such “typehint-like” assignments might suggest.

const x = { val: 0, name: "Quizzle" };

There are are two ways to create object enclosed literals:

  1. Using the type assertions on the property when it is defined. This allows you to selectively choose which properties are literals.
    const x = { val: 0, name: "Quizzle" as "Quizzle" }
    
  2. Using as const after the object assignment to make all properties literals that are immutable.
    const x = { val: 0, name: "Quizzle" } as const;
    

A full example of this behavior would be

const x = { val: 0, name: "Shima" };
x.val = 1;
x.name = "Buta";

const y = { val: 0, name: "Shima" as "Shima" };
y.val = 1;
y.name = "Buta"; // ERROR

const z = { val: 0, name: "Shima" } as const;
z.val = 1; // ERROR
z.name = "Buta"; // ERROR

Null

Non-null assertion operator

You can assert that a value is not null using the ! exclamation mark postfix operator. This circumnavigates the typescript compiler requiring null checking in your code for values that know will not be null and removes the null type from the value. You need to be careful with this as it is just away to get around compiler restrictions rather than actually performing any null checking.

const testString = (x?: string | null ) => {
    console.log(x!.toUpperCase());
}

testString("hello"); // => "hello"
testString(null); // ERROR

Unions

You can build up more complex types from previously defined types to create composite types, sort of like reverse inheritance where you move to a more general taxonomy of these from specific instances.

type Circle = { kind: "circle", radius: number };
type Square = { kind: "square", size: number };
type Shape = Circle | Square;

Discriminated Unions

The above Circle and Square types are an example of a discriminated union because there is a discriminator property, namely the kind property on each object that can be used to discriminate between object’s of the same class. This is a kind of bottom up definition of types using composites of these two object schemas into the Shape type (as opposed to a top down approach of something like inheritance). This allows you to write code like that below where a union with a common discriminating type can be called to narrow to a specific type that has specific values that are used.

const getShapeValue(shape: Shape) {
  // shape.kind is callable because both objects in the union have this field
  if (shape.kind == "circle") {
    return shape.radius;
  }
  return shape.size;
}

Never

If all types in a statement are eliminated then the passed in type becomes never which is also an error.