loading...
Cover image for 5 TypeScript features that will improve your codebase

5 TypeScript features that will improve your codebase

glebirovich profile image Gleb Irovich Updated on ・3 min read

Typescript 101 (5 Part Series)

1) 5 TypeScript features that will improve your codebase 2) 4 Ideas of how to harness the power of Typescript generic function 3) 5 Typescript utility types, that will make your life easier 4) Typescript Data Structures: Stack and Queue 5) Typescript Data Structures: Linked List

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:

  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! πŸš€

Typescript 101 (5 Part Series)

1) 5 TypeScript features that will improve your codebase 2) 4 Ideas of how to harness the power of Typescript generic function 3) 5 Typescript utility types, that will make your life easier 4) Typescript Data Structures: Stack and Queue 5) Typescript Data Structures: Linked List

Posted on by:

glebirovich profile

Gleb Irovich

@glebirovich

Tech Junkie | Pizza Lover πŸ• | Frontend Wizard πŸ§™β€β™‚οΈ | React Addicted πŸš€

Discussion

markdown guide
 

I would be wary of type guards. You are in general using them to check which type you are dealing with (in your example it is checking what type from a derived class you are using) and this is a code smell. You are breaking the Liskov substitution principle and could lead to some trouble down the line.
Great article though :)

 

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

 

Such type checking almost always causes troubles in the long run (any conditional checks on types), in my experience. Liskov Substitution is a great example. Luckily, we have polymorphism πŸ€“

 

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...

 

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