7 Utility in Typescript You Should Know
— typescript, javascript — 5 min read
Typescript has been my go-to language every time I start a new project. Typescript helps us to describe objects and to make inline documentation better. One of the things I love about typescript is it already provide utility function to facilitate common type transformations. This time we are going to talk about some of these utility types that I often use everyday.
1. Partial and Required
Partial<T> is the utility type that I use the most. This type makes all the properties of an interface optional.
type Cars = { brand: string color: string}
const helloCars = ( cars: Partial<Cars> ) => { if(cars.color) { console.log(`That is a ${cars.color} ${cars.brand}`) } else { console.log(`That is a ${cars.brand}`) }}
helloCars({ brand: "Honda", color: "yellow"}) // That is a yellow HondahelloCars({ brand: "Mercedes" }) // That is a MercedesIn the example above, we can see that we can call the helloCars function without defining the "colors" property because when we use Partial<Cars>, the object will change its annotation as you can see below.
// Partial<Cars> { brand?: string, color? string}In contrast to Partial<T>, Required<T> actually makes all fields of T required
// Required<Cars> { brand: string, color string}2. Readonly
As the name suggests, Readonly<T> makes all fields in the interface read-only. This means that they become immutable and cannot be reassigned.
interface Cars { brand: string color: string}
const myCars: Readonly<Cars> = { brand: "BMW", color: "white"}
// Cannot assign to 'brand' because it is a read-only property.myCars.brand = "Datsun"// Cannot assign to 'color' because it is a read-only property.myCars.color = "yellow"3. Record type
With Record<K,T> we can construct an object type using a union K as our keys and T as the value. Let's use the fields from the previous example.
type keys = "brand" | "color"
type Cars = Record<keys, string>
const helloCars = ( cars: Cars ) => { console.log(`You have a nice ${cars.color} ${cars.brand}`)}
helloCars({ brand: "BMW", color: "black" }) // You have a nice black BMWWith that union keys, you can add as many keys as you like. This will become a problem when you have a big object and wants to restrict the keys depends on an object. Luckily, we can solve this problem by using the keyof
interface Cars = { brand: string color: string}
const restrictedCars: Record<keyof Cars, string> = { brand: "Mercedes", color: "red", unexpectedKey: "value" // will not allowed since we only allowed 'brand' and 'color'}4. Pick and Omit
There will be a time when you just want to pick some keys from an interface and construct a new one. This is when Pick<I, K> comes into play. Pick<I, K> is a utility type that constructs an interface by picking K fields that are listed in I.
interface Cars = { brand: string color: string year: number}
interface PickCars = Pick<Cars, "color" | "year">
const restrictedCars: PickCars = { brand: "Mercedes", // will error since we just pick 'color' and 'year' from Cars color: "red", year: 2020}And there is Omit<I, K> that does the exact opposite of Pick<I,K, Omit<I,K> constructs an interface by picking all fields in I and then remove K keys.
interface Cars = { brand: string color: string year: number}
interface OmitCars = Omit<Cars, "brand">
const restrictedCars: OmitCars = { brand: "Mercedes", // will error since we omit 'brand' from Cars color: "red", year: 2020}The 2 examples above will return the same interface since they contain "color" and "year" in our constructed type.
5. Extract and Exclude
What if we want to Pick and Omit but on union type? Luckily, typescript provides a similar behavior with Extract and Exclude.
Extract<T, U> constructs a type by picking types from T that are a subset of U. However, Exclude<T, U> do the opposite by excluding types from T that are a subset of U. Let's see a few examples.
type AllDrinks = "teh" | "kopi" | "susu" | "jus" | 99 | (() => string)type DrinksWeLike = "teh" | "susu" | "bandrek"
type DrinksExtracted = Extract<AllDrinks, string> // "teh" | "kopi" | "susu" | "jus"type AvailableDrinks = Extract<DrinksExtracted, DrinkWeLike> // "teh" | "susu"
type DrinksExcluded = Exclude<AllDrinks, number | Function > // "teh" | "kopi" | "susu" | "jus"type DrinksWeHate = Exclude<DrinksExcluded, DrinkWeLike> // "kopi" | "jus"6. NonNullable
NonNullable<T> is a utility type that we can use to avoid null or undefined.
type Colors = "green" | "red" | undefined | null
type CorrectColors = NonNullable<Colors> // "green" | "red"But the interesting part is, this type is only worked if you use —strictNullChecks flag or set strictNullChecks: true on your tsconfig.
For example, this code is using strictNullChecks: false , as you can see the variable can still be assigned to undefined
and this is using strictNullChecks: true, the TS compiler will tell you immediately about the error.
7. ReturnType
ReturnType<F> returns the return type definition of F. To avoid an error, remember to always fill the F with function. Here is some example.
type stringF = ReturnType<() => string> // stringtype numberF = ReturnType<() => number> // numbertype unknownF = ReturnType<<T>() => T> // unknowntype wrongType1 = ReturnType<number> // Error: Type 'number' does not satisfy the constraint '(...args: any) => any'.type wrongType2 = ReturnType<Function> // Error: Type 'Function' does not satisfy the constraint '(...args: any) => any'.Moreover, you also can get a return type of an existing function by utilizing typeof
function helloCars() { return { brand: "Honda", color: "yellow" }}
type F = ReturnType<typeof helloCars>// type F = {// brand: string;// color: string;// }Conclusion
There are many other utility types you can use to help you construct your type but I hope these 7 utilities will help you to start understanding how to use them on your code. Please reach me on twitter on linkedIn if you have any questions or feedback. Hope we'll meet on another article, until then, stay great!