7 Secret TypeScript Tricks Pros Use

TypeScript is a superset of JavaScript that adds optional static typing and other features to the language. It is designed to make it easier to write and maintain large-scale JavaScript applications by catching errors before they occur at runtime. TypeScript is widely used in the industry and has a large community of developers who contribute to its development and maintenance

overwhelm

This article outlines 7 TypeScript tricks that will make your life easier which all pros use.

1. Type Inference

Typescript is smart enough to infer the data types when you help it narrow them down.

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;
  }
}

As shown above, TypeScript infers the type of the action based on the type property, so you DON'T need to check whether payload exists.

2. Literal Types

Often you need specific values for a variable, that's where literal types come in handy.

type Status = "idle" | "loading" | "success" | "error";

It works for numbers too:

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];

3. Type Guards

Type guards are another method to narrow down the type of a variable:

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: In the example above, it's better to use:

const validateAge = (age: number) => {
  // ...
};

The example was a simplification to show how type guards work.

4. Index Signature

When you have dynamic keys in an object, you can use an index signature to define its type:

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,
  // ...
};

5. Generics

Generics are a powerful tool to make your code more reusable. It allows you to define a type that will be determined by the use of your function.

In the following example, T is a Generic type:

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);

6. Immutable Types

You can make your types immutable by adding as const. This ensures that you don't end up accidentally changing the values.

const ErrorMessages = {
  InvalidEmail: "Invalid email",
  InvalidPassword: "Invalid password",
  // ...
} as const;

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

7. Partial, Pick, Omit & Required Types

Often while working with server & local data, you need to make some properties optional or required.

Instead of defining hundreds of interfaces with slightly altered versions of the same data. You can do that using Partial, Pick, Omit & Required types.

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;
// }

And of course, you can use intersection to combine them:

type A = B & C;

Where B & C are any types.

That's all folks! 🎉

Resources used

  1. Freepik
  2. Giphy

No comments:

Post a Comment