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
1type Person = {2 name: string3 location: {4 streetName: string5 streetNumber: number6 }7}
We can also create option types which specify what the allowed values are for a type
1type PersonType = 'work' | 'social' | 'other'2const personType: PersonType = 'work' // can't be any value other than what's allowed above
Below is an option type
1type ID = string | number
Here’s a type that uses the option type
1type OfficialData = {2 idNumber: ID3}
Types can be composed
Merging types
1type FullOfficalPerson = Person & OfficialData
Optional types
1type 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 atype
1interface BasePerson {2 name: string3 location: {4 streetName: string5 streetNumber: number6 }7}
Interfaces can be extended
Interface extension is similar to type merging, but with a bit of a different syntax
1interface AppUser extends BasePerson {2 websiteUrl: string3 twitterHandle?: string4 instagramHandle?: string5}
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)
1type MyArray1 = Array<ID>2type MyArray2 = ID[]
MyArray1 and MyArray2 are the same
Partial
Partial
makes all top-level properties of a type optional
1type PartialAppUser = Partial<AppUser>
The equivalent of the above with interfaces, where we extend a partial type to add an id
property
1interface CreateAppUser extends Partial<AppUser> {2 id: ID3}
Required
We can also have the Required
type, where all top-level props are required
1type 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
1type Contacts = Record<PersonType, ID>2
3const myContacts: Contacts = {4 work: 1234,5 social: 'hello',6 other: 0,7}
We can also have both values be option types
1const 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
1type AUser<T> = {2 id: T3}
And similarly, an interface based implementation
1interface BUser<T> {2 id: T3}
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`
1const bUser: BUser<number> = {2 // below, id must be a number3 id: 1234,4}
We can also use generics with multiple parameters like so:
1interface Thing<TID, TData> {2 id: TID3 data: TData4}
Values
Values are (an object or function which matches the type). We can use the above defs in order to define a value
1const 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
1console.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)
1type GetPerson = (id: ID) => Person2type GetPersonAsync = (id: ID) => Promise<Person>
We can also use generics for function definitions like so
1type GetThing<TID, TThing> = (id: TID) => TThing
In the below function, we say that the entire function is of type GetPerson
1const getPerson: GetPerson = (id) => person
In the below function, ID
is the type of the params, Promise<Person>
is the return type
1const getPersonAsync = async (id: ID): Promise<Person> => {2 return person3}
Classes
In general, we can also implement class properties like so
1class MyRespository {2 // below is a class member3 private data: Person[]4
5 // below is a constructir6 constructor(data: Person[]) {7 this.data = data8 }9}
However, it’s often desirable to create an interface that a class should amtch, for example
1interface IDRepository {2 get(id: ID): Promise<ID>3}
TID
below has a defualt value of ID
1interface Repository<TData, TID = ID> {2 ids: Array<TID>3 get(id: TID): Promise<TData>4}
A class can use that interface like so
1class 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 members15 public getIds = () => this.ids16}
Examples
Below is a link to the runnable Repl.it that has the above code defined