DEV Community

Cover image for TypeScript Guide: Everything you need to know! Part -2
Ankita Kanchan
Ankita Kanchan

Posted on

TypeScript Guide: Everything you need to know! Part -2

Welcome to Part 2 of our TypeScript journey! Now that you’re comfortable with the basics, it’s time to explore some of the more advanced concepts that can make your TypeScript skills truly shine. This guide will focus on practical techniques and best practices that you can start using in your daily coding routine.

Table of Contents

  1. Advanced Types
    • Union and Intersection Types
    • Literal Types
    • Type Guards
  2. Utility Types
    • Partial, Pick, and Omit
    • Record and Readonly
    • ReturnType and Parameters
  3. Generics: Supercharging Your Code
    • Generic Functions
    • Generic Constraints
    • Generic Classes and Interfaces
  4. Type Narrowing and Guards
    • Typeof and Instanceof
    • Custom Type Guards
  5. Advanced Interfaces and Type Aliases
    • Extending and Merging Interfaces
    • Combining Type Aliases with Interfaces
  6. Handling Asynchronous Code
    • Typing Promises
    • Async/Await with TypeScript
  7. TypeScript Configurations and Best Practices
    • Strict Mode and Compiler Options
    • Organizing Types and Interfaces
    • Avoiding Common Pitfalls

Advanced Types

TypeScript’s real power comes from its ability to handle complex types. These advanced types allow you to create more flexible and robust code.

Union and Intersection Types

Union Types let a variable be one of several types. They’re great when a value can have different forms.

type ID = string | number;
let userId: ID = 101; // Valid
userId = "abc123"; // Also valid
Enter fullscreen mode Exit fullscreen mode

Intersection Types combine multiple types into one. They’re useful for merging object shapes.

interface Person {
  name: string;
}

interface Employee {
  employeeId: number;
}

type EmployeeDetails = Person & Employee;

const emp: EmployeeDetails = {
  name: "Ankita",
  employeeId: 1234,
};
Enter fullscreen mode Exit fullscreen mode

Literal Types

Literal types restrict a variable to a specific set of values. This is particularly handy for defining options or statuses.

type Direction = "up" | "down" | "left" | "right";
let move: Direction = "up"; // Can only be one of these four values
Enter fullscreen mode Exit fullscreen mode

Type Guards

Type guards help TypeScript understand what type a variable has at runtime. They’re useful for narrowing down union types.

function printId(id: string | number) {
  if (typeof id === "string") {
    console.log(`ID is a string: ${id.toUpperCase()}`);
  } else {
    console.log(`ID is a number: ${id.toFixed(2)}`);
  }
}
Enter fullscreen mode Exit fullscreen mode

Utility Types

TypeScript provides several built-in utility types that simplify common transformations.

Partial, Pick, and Omit

  • Partial makes all properties in an object optional.
  interface User {
    name: string;
    age: number;
  }

  let partialUser: Partial<User> = { name: "John" };
Enter fullscreen mode Exit fullscreen mode
  • Pick creates a new type by selecting specific properties from an existing type.
  type UserPreview = Pick<User, "name">;
Enter fullscreen mode Exit fullscreen mode
  • Omit creates a new type by removing specific properties.
  type UserWithoutAge = Omit<User, "age">;
Enter fullscreen mode Exit fullscreen mode

Record and Readonly

  • Record is used to define an object type with specific key-value pairs.
  type Role = "admin" | "user" | "guest";
  const userRoles: Record<Role, string> = {
    admin: "Admin",
    user: "User",
    guest: "Guest",
  };
Enter fullscreen mode Exit fullscreen mode
  • Readonly makes all properties in an object immutable.
  const config: Readonly<User> = {
    name: "Jane",
    age: 28,
  };
Enter fullscreen mode Exit fullscreen mode

ReturnType and Parameters

  • ReturnType extracts the return type of a function.
  function fetchUser(): User {
    return { name: "Ankita", age: 26 };
  }

  type UserType = ReturnType<typeof fetchUser>;
Enter fullscreen mode Exit fullscreen mode
  • Parameters extracts the parameter types of a function as a tuple.
  type FetchUserParams = Parameters<typeof fetchUser>;
Enter fullscreen mode Exit fullscreen mode

Generics: Supercharging Your Code

Generics allow you to create reusable components that work across a variety of types.

Generic Functions

Generics are perfect for functions that work with multiple data types.

function identity<T>(value: T): T {
  return value;
}

let num = identity<number>(42);
let word = identity<string>("TypeScript");
Enter fullscreen mode Exit fullscreen mode

Generic Constraints

You can constrain generics to ensure they work only with specific types.

function getProperty<T, K extends keyof T>(obj: T, key: K) {
  return obj[key];
}

let person = { name: "John", age: 30 };
let name = getProperty(person, "name"); // Valid
Enter fullscreen mode Exit fullscreen mode

Generic Classes and Interfaces

Generics can be used in classes and interfaces to make them more flexible.

class Box<T> {
  content: T;
  constructor(content: T) {
    this.content = content;
  }
}

let stringBox = new Box<string>("Hello");
Enter fullscreen mode Exit fullscreen mode

Type Narrowing and Guards

TypeScript can infer the type of a variable using type guards and control flow analysis.

Typeof and Instanceof

Use typeof and instanceof to narrow down types in your code.

function padLeft(value: string | number, padding: string | number) {
  if (typeof padding === "number") {
    return " ".repeat(padding) + value;
  }
  if (typeof padding === "string") {
    return padding + value;
  }
}
Enter fullscreen mode Exit fullscreen mode

Custom Type Guards

You can create your own type guards for more complex checks.

interface Dog {
  bark: () => void;
}

interface Cat {
  meow: () => void;
}

function isDog(pet: Dog | Cat): pet is Dog {
  return (pet as Dog).bark !== undefined;
}
Enter fullscreen mode Exit fullscreen mode

Advanced Interfaces and Type Aliases

Interfaces and type aliases can be combined and extended in various ways to model complex data structures.

Extending and Merging Interfaces

Interfaces can be extended to add more properties:

interface Animal {
  name: string;
}

interface Dog extends Animal {
  breed: string;
}
Enter fullscreen mode Exit fullscreen mode

Combining Type Aliases with Interfaces

You can combine interfaces and type aliases for maximum flexibility.

type Pet = Dog & { age: number };
Enter fullscreen mode Exit fullscreen mode

Handling Asynchronous Code

Working with async code is a crucial part of modern development, and TypeScript makes it easier.

Typing Promises

Make sure your async functions are well-typed:

async function fetchData(): Promise<string> {
  return "Data fetched!";
}
Enter fullscreen mode Exit fullscreen mode

Async/Await with TypeScript

TypeScript works smoothly with async/await patterns:

async function getUserData(): Promise<User> {
  const response = await fetch("/api/user");
  return await response.json();
}
Enter fullscreen mode Exit fullscreen mode

TypeScript Configurations and Best Practices

TypeScript is highly configurable. Here’s how to set it up for success.

Strict Mode and Compiler Options

Enabling strict mode gives you the best type-checking experience:

{
  "compilerOptions": {
    "strict": true,
    "noImplicitAny": true,
    "strictNullChecks": true
  }
}
Enter fullscreen mode Exit fullscreen mode

Organizing Types and Interfaces

Keep your types and interfaces organized in a separate file, especially in larger projects. Use folders and consistent naming conventions to make your codebase easy to navigate.

Avoiding Common Pitfalls

  1. Overusing any: It’s tempting to use any when you hit a roadblock, but try to use it sparingly.
  2. Ignoring Errors: Address TypeScript errors instead of bypassing them – they usually signal real issues in your code.
  3. Not Using Type Inference: TypeScript can often infer types without you explicitly stating them. Don’t overdo type annotations where they’re unnecessary.

Conclusion

This guide covered some of the more advanced and practical features of TypeScript that can make your code cleaner, more robust, and easier to maintain. As you continue your journey, remember that TypeScript is all about striking the right balance between safety and flexibility.

Start incorporating these techniques into your daily coding, and you’ll soon find yourself writing better, more reliable code!

If you have any questions or need further tips, feel free to ask in the comments!

Happy coding! 🎉

Top comments (0)