Check the typescript handbook
// enum
enum Role {
ADMIN,
READ_ONLY,
AUTHOR
}
// object
type Person = {
readonly id: string | number; // readonly, cannot be re-assigned
firstName: string; // string
lastName: string; // string
fullName: (firstName: string, lastName: string ) => string; // function with two params string and returns string
age: number; // number
month: number | string; // union
hobbies: string[]; // array
childBornAt: (number | string)[]; // array of string or number
keyValuePair: [string, number]; // turple: we are sure the first element is a string and the second number
role: Role;
callback: (num: number) => void; // function that doesnt return anything
}
For objects this is preferable
interface Indexed {
readonly id: string | number;
}
interface Named {
firstName: string; // string
middleName?: string; // optional
lastName: string; // string
}
interface Greetable extends Named, Indexed {
greet: (name: string) => string
}
// use it like so
const Manel: Greetable = {
id: 123,
firstName: 'Manel',
lastName: 'Paiva',
greet: (name) => `Hello ${name}`
}
Can also be used for function types
interface AddFn {
(a: number, b: number): number
}
const add : AddFn = (a, b) => a + b
type Admin = {
name: string
priviliges: string[]
}
type Employee = {
name: string
startDate: Date
}
// in objects combines them
type ElevatedUser = Employee & Admin
const admin: ElevatedUser = {
name: 'Joao',
priviliges: ['delete users'],
startDate: new Date()
}
type Combinable = string | number
type Numeric = number | boolean
// in union types intersects whats in common
type Universal = Combinable & Numeric // number
function add(a: Combinable, b: Combinable) {
// check if its a string before adding
if (typeof a === 'string' || typeof b === 'string') {
return a.toString() + b.toString()
}
return a + b
}
Objects
type UnknownEmployee = Employee | ElevatedUser
function printEmployeeInformation(emp: UnknownEmployee) {
console.log('Name: ', emp.name)
// check if the UnknownEmployee has the priviliges key
if ('priviliges' in emp) {
console.log('Privileges: ', emp.priviliges)
}
}
Class instances
class Car {
drive() {
console.log('Driving...')
}
}
class Truck {
drive() {
console.log('Driving...')
}
loadCargo(ammount: number): void {
console.log('Loading: ', ammount)
}
}
type Vehicle = Car | Truck
const mercedez = new Car()
const man = new Truck()
function useVehicle(vehicle: Vehicle) {
vehicle.drive()
// check if the vehicle is an instance of truck
if (vehicle instanceof Truck) vehicle.loadCargo(2000)
}
// this element is of type `HTMLInputElement` and will never be null `!`
const userInputElement = <HTMLInputElement>document.getElementById('user-input')!
// alternative to prevent conflicts with JSX
const userInputElementAlt = document.getElementById('user-input') as HTMLInputElement
// alternative to use `!`
if (userInputElementAlt) {
(userInputElementAlt as HTMLInputElement).value = 'Hi there'
}
I dont know the property names but the property name and value must be a string, allows for "dynamic object types".
interface ErrorContainer {
[prop: string]: string;
}
const errorBag: ErrorContainer = {
email: 'Not a valid email',
username: 'Must start with a capital character!'
}
Telling typescript the different combinations you might support in your function
function add (a: number, b: number): number
function add (a: string, b: string): string
function add (a: number, b: string): string
function add (a: string, b: number): string
function add(a: Combinable, b: Combinable) {
// check if its a string before adding
if (typeof a === 'string' || typeof b === 'string') {
return a.toString() + b.toString()
}
return a + b
}
const result = add(1, 'Manel')
result.split(' ')
// imagine this data was coming from an API:
const data = {
id: 'u1',
name: 'Antonio',
job: {
// title: 'CEO',
description: 'My own company'
}
}
// will only log data.job.title if it exists
console.log(data?.job?.title)
// in javascript:
console.log(data && data.job && data.job.title)
A logical operator that returns its right-hand side operand when its left-hand side operand is null or undefined, prevents unexpected behaviors.
// if you use || to provide some default value to another variable foo
//, you may encounter unexpected behaviors if you consider some falsy values as usable (eg. '' or 0).
const foo = null ?? 'default string';
console.log(foo);
// expected output: "default string"
const baz = 0 ?? 42;
console.log(baz);
// expected output: 0
Get additional type information Array<string>
array of strings, Promise<string>
promise that resolves a string...
const names = Array<string> = []; // string[]
const promise: Promise<string> = new Promise((resolve) => {
setTimeout(() => {
resolve('yoo')
}, 2000)
})
Custom:
function merge<T extends object, U extends object>(objectA: T, objectB: U) {
return Object.assign(objectA, objectB)
}
interface Lengthy {
length: number;
}
const countAndDescribe = <T extends Lengthy>(element: T): [T, string] => {
let descriptionText = 'Got no value'
if (element.length === 1) descriptionText = 'Got 1 element'
else if (element.length > 1) descriptionText = `Got ${element.length} elements`
return [element, descriptionText]
}
keyof constraint:
function extractAndConvert<T extends Object, U extends keyof T>(obj: T, key: U) {
return obj[key]
}
Generic utility types
Partial type:
interface User {
name: string;
}
function createUser(name: string): User {
// in the end it will become of type User
let newUser: Partial<User> = {}
newUser.name = name
// type casting to type User
return newUser as User
}
Readonly type
// you can't change later: extra strictness
const admins: Readonly<string[]> = ['Antonio', 'Manel']