Typescript Basics

Introduction and cheatsheet for basic Typescript concepts

Updated: 03 September 2023

Types

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

1
type Person = {
2
name: string
3
location: {
4
streetName: string
5
streetNumber: number
6
}
7
}

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

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

Below is an option type

1
type ID = string | number

Here’s a type that uses the option type

1
type OfficialData = {
2
idNumber: ID
3
}

Types can be composed

Merging types

1
type FullOfficalPerson = Person & OfficialData

Optional types

1
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

1
interface BasePerson {
2
name: string
3
location: {
4
streetName: string
5
streetNumber: number
6
}
7
}

Interfaces can be extended

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

1
interface AppUser extends BasePerson {
2
websiteUrl: string
3
twitterHandle?: string
4
instagramHandle?: string
5
}

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)

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

MyArray1 and MyArray2 are the same

Partial

Partial makes all top-level properties of a type optional

1
type PartialAppUser = Partial<AppUser>

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

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

Required

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

1
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

1
type Contacts = Record<PersonType, ID>
2
3
const myContacts: Contacts = {
4
work: 1234,
5
social: 'hello',
6
other: 0,
7
}

We can also have both values be option types

1
const associations: Record<PersonType, PersonType> = {
2
work: 'other',
3
social: 'work',
4
other: 'other',
5
}

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

1
type AUser<T> = {
2
id: T
3
}

And similarly, an interface based implementation

1
interface BUser<T> {
2
id: T
3
}

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`

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

We can also use generics with multiple parameters like so:

1
interface Thing<TID, TData> {
2
id: TID
3
data: TData
4
}

Values

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

1
const person: Person = {
2
name: 'Bob',
3
location: {
4
streetName: 'My Street',
5
streetNumber: 24,
6
},
7
}

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

1
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)

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

We can also use generics for function definitions like so

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

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

1
const getPerson: GetPerson = (id) => person

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

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

Classes

In general, we can also implement class properties like so

1
class MyRespository {
2
// below is a class member
3
private data: Person[]
4
5
// below is a constructir
6
constructor(data: Person[]) {
7
this.data = data
8
}
9
}

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

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

TID below has a defualt value of ID

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

A class can use that interface like so

1
class PersonRepository implements Repository<Person, ID> {
2
ids = [1, 2, 3]
3
4
async get(id) {
5
return {
6
name: 'Bob',
7
location: {
8
streetName: 'My Street',
9
streetNumber: 24,
10
},
11
}
12
}
13
14
// we can use access modifiders on members
15
public getIds = () => this.ids
16
}

Examples

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

Repl.it link

Other Relevant Docs