Hi, I'm Rodo 👋

I'm a Software Engineer

Rodolfo Guluarte Hale

Hi, I'm Rodo 👋

I'm a Software Engineer

Unveiling 7 expert TypeScript techniques pros utilize for enhanced productivity 🎩🔮

4 minutes
May 19, 2023

Embracing TypeScript can be a game-changer for coding efficiency and bug prevention. However, it can sometimes feel a bit daunting to use.

In this insightful piece, we explore 7 expert TypeScript techniques that can simplify your coding journey, as employed by seasoned professionals.

  1. Leveraging Type Inference The inherent intelligence of TypeScript enables it to infer data types when guided to do so.
enum CounterActionType {
  Increment = "INCREMENT",
  IncrementBy = "INCREMENT_BY",
}

interface IncrementAction {
  type: CounterActionType.Increment;
}

interface IncrementByAction {
  type: CounterActionType.IncrementBy;
  payload: number;
}

type CounterAction =
  | IncrementAction
  | IncrementByAction;

function reducer(state: number, action: CounterAction) {
  switch (action.type) {
    case CounterActionType.Increment:
      // TS infers that the action is IncrementAction
      // & has no payload
      return state + 1;
    case CounterActionType.IncrementBy:
      // TS infers that the action is IncrementByAction
      // & has a number as a payload
      return state + action.payload;
    default:
      return state;
  }
}

In this scenario, TypeScript deduces the action’s type based on the type property, removing the need for you to verify whether a payload is present.

  1. Usage of Literal Types Literal types come in handy when specific variable values are required.
type Status = "idle" | "loading" | "success" | "error";

Literal types are not limited to strings; they work with numbers as well. In fact, you can further simplify things by using a map.

type Review = 1 | 2 | 3 | 4 | 5;

// or better yet:
const reviewMap = {
  terrible: 1,
  average: 2,
  good: 3,
  great: 4,
  incredible: 5,
} as const;

// This will generate the same type as above,
// but it's much more maintainable
type Review = typeof reviewMap[keyof typeof reviewMap];
  1. Application of Type Guards Type guards offer another strategy to narrow down a variable’s type:
function isNumber(value: any): value is number {
  return typeof value === "number";
}

const validateAge = (age: any) => {
  if (isNumber(age)) {
    // validation logic
    // ...
  } else {
    console.error("The age must be a number");
  }
};

Note: The example has been simplified for illustrative purposes. Ideally, it is preferable to use the specific type in your function, such as const validateAge = (age: number) => {...};.

  1. Deploying Index Signature An index signature can be used to define the type when you’re dealing with dynamic keys in an object.
enum PaticipationStatus {
  Joined = "JOINED",
  Left = "LEFT",
  Pending = "PENDING",
}

interface ParticipantData {
  [id: string]: PaticipationStatus;
}

const participants: ParticipantData = {
  id1: PaticipationStatus.Joined,
  id2: PaticipationStatus.Left,
  id3: PaticipationStatus.Pending,
  // ...
};
  1. Implementing Generics Generics are a powerful way to enhance the reusability of your code. They enable you to set a type determined by the function usage.
const clone = <T>(object: T) => {
  const clonedObject: T = JSON.parse(JSON.stringify(object));
  return clonedObject;
};

const obj = {
  a: 1,
  b: {
    c: 3,
  },
};

const obj2 = clone(obj);
  1. Creating Immutable Types By adding as const, you can make your types immutable. This ensures accidental value changes are prevented.
const ErrorMessages = {
  InvalidEmail: "Invalid email",
  InvalidPassword: "Invalid password",
  // ...
} as const;

// This will throw an error
ErrorMessages.InvalidEmail = "New error message";

Attempting to modify these immutable types will result in an error.

  1. Using Partial, Pick, Omit & Required Types While handling server and local data, it’s often necessary to make some properties optional or required.
interface User {
  name: string;
  age?: number;
  email: string;
}

type PartialUser = Partial<User>;
type PickUser = Pick<User, "name" | "age">;
type OmitUser = Omit<User, "age">;
type RequiredUser = Required<User>;

// PartialUser is equivalent to:
// interface PartialUser {
//   name?: string;
//   age?: number;
//   email?: string;
// }

// PickUser is equivalent to:
// interface PickUser {
//   name: string;
//   age?: number;
// }

// OmitUser is equivalent to:
// interface OmitUser {
//   name: string;
//   email: string;
// }

// RequiredUser is equivalent to:
// interface RequiredUser {
//   name: string;
//   age: number;
//   email: string;
// }

You can also combine them using intersection: type A = B & C; where B & C are any types.

That’s a wrap, folks! 🎉