Typescript is a godsend. It is very easy to get started with and for most developers there is no way back once they get the hang of it. Sometimes it can get pretty advanced and intimidating though.
This is why I decided to share 4 of my favourite typescript tips and tricks you might have needed in the past. Some are super basic, some are bit more advanced.
Higher-order Component
In React higher order components (HOC) are very useful tools. Generally they are used to wrap some layout or functionality to some other components. They are simply functions that return another component, basically the same pattern as decorators.
In typescript it can be confusing how to write them maintaining the right after wrapping the original component. Here you are:
import React from 'react'
function withLayout<P extends object>(WrappedComponent: React.ComponentType<P>) {
return (props: P) => (
<div id='app'>
<Header/>
<WrappedComponent {...props}/>
<Footer/>
</div>
);
}
Smarter constructors
Let’s start by the basic building block this is based on. It’s a basic Javascript trick, not a typescript exclusive at first.
class Pizza {
slices: number
name: string
constructor(init) {
Object.assign(this, init)
}
}
const pizza = new Pizza({
slices: 8,
name: 'Margherita',
})
What is happening here? With the super handy Object.assign
simply assigns the object to the class. This is super handy when classes have many constructor parameters. But this is NOT type safe as your IDE/Editor will tell you. How do we fix this?
import { NonFunctionKeys } from 'utility-types'
class Pizza {
slices!: number
name?: string
constructor(init: Pick<Pizza, NonFunctionKeys<Pizza>>) {
Object.assign(this, init)
}
eat() {
this.slices = 0
}
}
const pizza = new Pizza({
slices: 8,
name: 'Margherita',
})
Let me explain what happens:
This leverages the awesome utility-types package. We first take all the keys that are not a function, so we don’t overwrite the eat
method of the class. Then we pick those from the general Pizza type.
This means that slices
will be required, while name
will be optional, as they are defined.
Type-checking Functions
Did you know you can write functions to tell typescript what type something is? This is awesome!
Suppose we have the following interfaces
interface Food {
name: string
}
interface Pasta extends Food {
type: 'Spaghetti' | 'Fusilli'
}
interface Pizza extends Food {
slices: number
}
Now we could write a cook
function that accepts both Pasta and Pizza. Typescript itself cannot differentiate between the too.
function cook(what: Food) {
if(what === Pizza) ????
}
Fortunately there is a nice solution built into typescript.
function isPizza(x: Food | Pizza): x is Pizza {
return x.hasOwnProperty('slices')
}
function isPasta(x: Food | Pasta): x is Pasta {
return x.hasOwnProperty('type')
}
function cook(plate: Food) {
if (isPizza(plate)) {
// Plate is now of type Pizza
putInTheOven(plate)
}
if (isPasta(plate)) {
// Plate is now of type Pasta
putInThePan(plate)
}
}
Here we define two functions that return x is Sometype
and return a boolean value based on the input. It's up to you of course to define it properly, but this can be very useful in various situations.
Excluding object types
type Sqlite = {
type: 'sqlite',
database: string,
}
type PostgreSQL = {
type: 'postgresql',
database: string,
host: string,
post?: number
}
type PossibleConfigs = Sqlite | PostgreSQL
function initialize(config: PossibleConfigs) {}
This might look like a simple one, but I often see people putting those sorts of types all into the same interface. By separating the different type of objects you make sure that they are safe. Also the autocomplete will thank you.