The emergence of typescript changed the way how developers deal with javascript applications. It brings not only code reliability with type-safety, but also improves developer experience, by providing powerful refactoring capabilities and smarter ways to write JS code.
In this post I would like to talk about five typescript features, that will improve your codebase.
Table of content
1. Nullish coalescing
Let's consider the following example.
function getNumberOrDefault(value?: number) {
return value || 22;
}
In the mentioned example, we would like to return a default value, if the provided value is undefined. However, this code has a severe pitfall — javascript treats 0
as a falsy
value.
console.log(getNumberOrDefault(10)) // Output 10
console.log(getNumberOrDefault(0)) // Output 22
Returning 22
is probably, not something we expect this function to do. Thanks to the nullish coalescing we don't need to refactor the code and add check for 0
, instead we can use a shorthand syntax with ??
.
function getNumberOrDefaultTs(value?: number) {
return value ?? 22;
}
console.log(getNumberOrDefault(10)) // Output 10
console.log(getNumberOrDefault(0)) // Output 0
2. Type guards
Let's imagine we have two types, Truck
and F1
, which extend the same interface Car
. Although they share some common properties, they also possess a set of unique attributes like load
and acceleration
respectively.
interface Car {
model: string;
}
interface Truck extends Car {
load: number;
}
interface F1 extends Car {
acceleration: number;
}
function scanCar(car: Truck | F1) {
...some code
}
Also, we have a function scanCar
which accepts either a Truck
or a F1
and performs some type-specific actions.
Type guard is a great way to let typescript know with which type we are currently dealing.
function carIsTruck(car: Truck | F1): car is Truck {
return 'load' in car;
}
function scanCar(car: Truck | F1) {
if(carIsTruck(car)) {
// Only truck properties will be suggested
console.log(`Truck has a load of ${car.load}`)
} else {
// Only F1 properties will be suggested
console.log(`F1 has acceleration of ${car.acceleration}`)
}
}
Type guard is a function that returns a boolean, which is often used as part of the conditional statement, to let typescript know which type is assumed in the current if block.
3. Enum object keys
There are some cases when we want to enforce object keys being certain values only. And here is how we can achieve it using enum
and Record
.
enum Size {
M,
L,
XL
}
function getPlainTextSizes(): Record<Size, string> {
return {
[Size.M]: "medium",
[Size.L]: "large",
[Size.XL]: "extra large",
10: "small", // error
"size": "xs" // error
}
}
Record
is a generic type util, which allows defining types for key-value maps.
4. Generic interfaces
interface Item<T> {
id: number,
value: T,
}
interface Post {
title: string;
}
const item1: Item<number> = { id: 1, value: 10 }
const item2: Item<Post> = { id: 1, value: { title: "Post name" } }
Generics in typescript significantly improve code reusability.
5. Generic function parameter types
interface Book {
id: number;
author: string;
}
interface Recipe {
id: number;
cookingTime: number;
}
function mapById<T extends { id: number }>(array: T[]): {[key: number]: T} {
return array.reduce((map, item) => ({ ...map, [item.id]: item }), {})
}
const books: Book[] = [{ id: 1, author: "A" }];
const recipies: Recipe[] = [{ id: 1, cookingTime: 10 }];
mapById(books);
mapById(recipies);
It's often the case that we have util functions which require only some field from the interface. In the example above you can see a function which takes an array as input and returns values mapped by id. The only important thing is that the items of the array have an id
. When you pass a parameter to the function typescript will:
- Infere the type of the provided parameter
- Check if the parameter matches specified condition (must have
id
of type number) So now you are save to use this helper for various interfaces.
Summary
As you can see typescript provides a lot of tools to write reusable and flexible code without sacrificing type safety.
Thanks for reading! This post is an experiment with a new format, let me know if you find it interesting and helpful. Help to spread the word and follow me on Twitter for more cool stuff about web dev! 🚀
Top comments (4)
In the type guard example truck with
load
of0
will be considered asF1
because0
is falsy. It is better to check usingin
operator:Such check can be used even without writing type guard function:
typescriptlang.org/docs/handbook/a...
Good catch regarding the
load
, I will update the example.You are also right, that we can use it inline, but the idea was also to discuss the type guards. If this check is extracted it can be reused across the app if necessary and it also improves readability of the code.
Nullish coalescing is built in to JS and already available in almost all browsers
Thanks for the feedback. Yep, probably , I found not the best example.