Typescript Basics

Introduction and cheatsheet for basic Typescript concepts

Types

A type is the most basic thing in typescript and is an object structure

type Person = {
  name: string
  location: {
    streetName: string
    streetNumber: number
  }
}

We can also create option types which specify what the allowed values are for a type

type PersonType = 'work' | 'social' | 'other'
const personType: PersonType = 'work' // can't be any value other than what's allowed above

Below is an option type

type ID = string | number

Here's a type that uses the option type

type OfficialData = {
  idNumber: ID
}

Types can be composed

Merging types

type FullOfficalPerson = Person & OfficialData

Optional types

type MinOfficialPerson = Person | OfficialData | ID

Interfaces

Next, we have interfaces, it's like a type, but can't be composed like the above types

Note that an interface doesn't use = like a type

interface BasePerson {
  name: string
  location: {
    streetName: string
    streetNumber: number
  }
}

Interfaces can be extended

Interface extension is similar to type merging, but with a bit of a different syntax

interface AppUser extends BasePerson {
  websiteUrl: string
  twitterHandle?: string
  instagramHandle?: string
}

Type helpers

Typescript provides us with some base types that we can use to achieve interesting compositions

Arrays

Array types, can use Array or [] (both are the same)

type MyArray1 = Array<ID>
type MyArray2 = ID[]

MyArray1 and MyArray2 are the same

Partial

Partial makes all top-level properties of a type optional

type PartialAppUser = Partial<AppUser>

The equivalent of the above with interfaces, where we extend a partial type to add an id property

interface CreateAppUser extends Partial<AppUser> {
  id: ID
}

Required

We can also have the Required type, where all top-level props are required

type FullAppUser = Required<AppUser>

Records

Another useful one is the Record type. which lets us specify an object's key and value type. Usually one or both of these are an option type

type Contacts = Record<PersonType, ID>

const myContacts: Contacts = {
  work: 1234,
  social: 'hello',
  other: 0
}

We can also have both values be option types

const associations: Record<PersonType, PersonType> = {
  work: 'other',
  social: 'work',
  other: 'other'
}

Generics

In the above examples, the helper types are using generics. Below is a generic that allows us to specify a user with type of an id

type AUser<T> = {
  id: T
}

And similarly, an interface based implementation

interface BUser<T> {
  id: T;
}

Although we can use T for the generic, (or any other letter), we usually give it something more meaningful. e.g. Key/Value pairs, use K and V, or a type of Data may be TData`

const bUser: BUser<number> = {
  // below, id must be a number
  id: 1234
}

We can also use generics with multiple parameters like so:

interface Thing<TID, TData> {
  id: TID
  data: TData
}

Values

Values are (an object or function which matches the type). We can use the above defs in order to define a value

const person: Person = {
  name: 'Bob',
  location: {
    streetName: 'My Street',
    streetNumber: 24
  }
}

Also note that you cannot log or use a type as data, e.g. the following will be an error

console.log(Person)

This is because types don't exist at runtime. they're just a developer tool

Functions

Types can also be used to defined functions (interfaces can't do this)

type GetPerson = (id: ID) => Person
type GetPersonAsync = (id: ID) => Promise<Person>

We can also use generics for function definitions like so

type GetThing<TID, TThing> = (id: TID) => TThing

In the below function, we say that the entire function is of type GetPerson

const getPerson: GetPerson = (id) => person

In the below function, ID is the type of the params, Promise<Person> is the return type

const getPersonAsync = async (id: ID): Promise<Person> => {
  return person
}

Classes

In general, we can also implement class properties like so

class MyRespository {
  // below is a class member
  private data: Person[]

  // below is a constructir
  constructor(data: Person[]) {
    this.data = data
  }
}

However, it's often desirable to create an interface that a class should amtch, for example

interface IDRepository {
  get(id: ID): Promise<ID>
}

TID below has a defualt value of ID

interface Repository<TData, TID = ID> {
  ids: Array<TID>
  get(id: TID): Promise<TData>
}

A class can use that interface like so

class PersonRepository implements Repository<Person, ID> {
  ids = [1, 2, 3]

  async get(id) {
    return {
      name: 'Bob',
      location: {
        streetName: 'My Street',
        streetNumber: 24
      }
    }
  }

  // we can use access modifiders on members
  public getIds = () => this.ids
}

Examples

Below is a link to the runnable Repl.it that has the above code defined

Repl.it link

Other Relevant Docs