DEV Community

Cover image for 5 TypeScript features that will improve your codebase
Gleb Irovich
Gleb Irovich

Posted on • Updated on

5 TypeScript features that will improve your codebase

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;
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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}`)
  }
}
Enter fullscreen mode Exit fullscreen mode

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
  }
}
Enter fullscreen mode Exit fullscreen mode

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" } }
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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:

  1. Infere the type of the provided parameter
  2. 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)

Collapse
 
jonrandy profile image
Jon Randy 🎖️

Nullish coalescing is built in to JS and already available in almost all browsers

Collapse
 
nekitk profile image
Golubov Nikita

In the type guard example truck with load of 0 will be considered as F1 because 0 is falsy. It is better to check using in operator:

function carIsTruck(car: Truck | F1): car is Truck {
  return 'load' in car;
}

Such check can be used even without writing type guard function:
typescriptlang.org/docs/handbook/a...

Collapse
 
glebirovich profile image
Gleb Irovich

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.

Collapse
 
glebirovich profile image
Gleb Irovich

Thanks for the feedback. Yep, probably , I found not the best example.