DEV Community

Cover image for Master TypeScript with These Essential Concepts
PRINCE KUKREJA
PRINCE KUKREJA

Posted on

Master TypeScript with These Essential Concepts

This quarter, one of my company goals was to dive deeper into TypeScript using TypeHero challenges. I want to share my learnings about some of TypeScript's most powerful features. No matter where you are in your coding journey—whether you're a seasoned pro or a beginner—this guide will provide you with the foundational knowledge to confidently dive into TypeScript.

So, let's get typing!


Table of Contents

Primitive Data Types

TypeScript includes all JavaScript primitive types: string, number, boolean, null, undefined, symbol, and bigint. These form the foundation of the type system:

let name: string = "Alice";
let age: number = 25;
let isActive: boolean = true;
let nothing: null = null;
let notDefined: undefined = undefined;
let uniqueSymbol: symbol = Symbol("id");
let bigNumber: bigint = 9007199254740991n;
Enter fullscreen mode Exit fullscreen mode

Type Aliases

Type aliases let you create custom names for types. They're great for reusability and making your code more readable:

type UserID = string;
type Point = {
  x: number;
  y: number;
};

// Using the types
const userId: UserID = "user123";
const coordinate: Point = { x: 10, y: 20 };
Enter fullscreen mode Exit fullscreen mode

Literal Types

Literal types allow you to specify exact values that a type can have:

type Direction = "north" | "south" | "east" | "west";
type DiceRoll = 1 | 2 | 3 | 4 | 5 | 6;

function move(direction: Direction, spaces: DiceRoll) {
  console.log(`Moving ${spaces} spaces ${direction}`);
}

move("north", 3); // Valid
// move("northeast", 7); // Error: Invalid direction and number
Enter fullscreen mode Exit fullscreen mode

Type Unions

Unions allow a value to be one of several types. They're perfect for handling multiple possible types:

type StringOrNumber = string | number;
type Status = "loading" | "success" | "error";

function processId(id: StringOrNumber) {
  if (typeof id === "string") {
    console.log(id.toUpperCase());
  } else {
    console.log(id.toFixed(2));
  }
}
Enter fullscreen mode Exit fullscreen mode

Generic Function Arguments

Generics make functions more flexible by allowing them to work with different types while maintaining type safety:

function firstElement<T>(arr: T[]): T | undefined {
  return arr[0];
}

// TypeScript infers the correct return types
const numberResult = firstElement([1, 2, 3]); // type: number
const stringResult = firstElement(["a", "b", "c"]); // type: string
Enter fullscreen mode Exit fullscreen mode

Think of them as placeholders for different data types within a function.

Generic Type Arguments

Generic type arguments work similarly but for type definitions:

type Box<T> = {
  content: T;
  timestamp: Date;
};

const stringBox: Box<string> = {
  content: "Hello",
  timestamp: new Date()
};

const numberBox: Box<number> = {
  content: 42,
  timestamp: new Date()
};
Enter fullscreen mode Exit fullscreen mode

Like function arguments, but for defining flexible data structures.

Default Generic Arguments

You can specify default types for generics, similar to default function parameters:

interface ApiResponse<T = any> {
  data: T;
  status: number;
  message: string;
}

// Uses the default 'any' type
const simpleResponse: ApiResponse = {
  data: "anything",
  status: 200,
  message: "Success"
};

// Specifies a custom type
const typedResponse: ApiResponse<string[]> = {
  data: ["item1", "item2"],
  status: 200,
  message: "Success"
};
Enter fullscreen mode Exit fullscreen mode

It provides a fallback type if none is explicitly specified.

Generic Type Constraints

Constraints limit what types can be used with generics using the extends keyword:

interface HasLength {
  length: number;
}

function logLength<T extends HasLength>(item: T): void {
  console.log(item.length);
}

logLength("hello"); // Works: strings have length
logLength([1, 2, 3]); // Works: arrays have length
// logLength(123); // Error: numbers don't have length
Enter fullscreen mode Exit fullscreen mode

Ensure that only specific types can be used in a generic context.

Index Signatures

Index signatures allow you to type objects with dynamic property names:

type Dictionary = {
  [key: string]: string;
};

const colors: Dictionary = {
  red: "#FF0000",
  green: "#00FF00",
  blue: "#0000FF"
};

// Any string key is allowed
colors.purple = "#800080";
Enter fullscreen mode Exit fullscreen mode

Define the shape of objects with a variable number of properties.

Indexed Types

Indexed types let you access the type of a property in another type:

interface User {
  name: string;
  age: number;
  email: string;
}

type AgeType = User["age"]; // type: number
type ContactInfo = User["email" | "name"]; // type: string
Enter fullscreen mode Exit fullscreen mode

Access and manipulate the types of specific properties within an object.

The keyof Operator

keyof gets all property names from a type as a union:

interface Product {
  id: number;
  name: string;
  price: number;
}

type ProductKeys = keyof Product; // "id" | "name" | "price"

function getProperty(obj: Product, key: ProductKeys) {
  return obj[key];
}
Enter fullscreen mode Exit fullscreen mode

Extract all possible property names from a given type.

The typeof Operator

In type contexts, typeof creates a type based on the type of a value:

const user = {
  name: "Alice",
  age: 25,
  role: "admin"
};

type User = typeof user;

// Equivalent to:
// type User = {
//   name: string;
//   age: number;
//   role: string;
// }
Enter fullscreen mode Exit fullscreen mode

Determine the type of a variable or expression at compile time.

Mapped Object Types

Mapped types let you create new types based on old ones by transforming properties:

type Optional<T> = {
  [K in keyof T]?: T[K];
};

type ReadOnly<T> = {
  readonly [K in keyof T]: T[K];
};

interface Todo {
  title: "string;"
  description: "string;"
}

type OptionalTodo = Optional<Todo>;
// Equivalent to:
// {
//   title?: string;
//   description?: string;
// }
Enter fullscreen mode Exit fullscreen mode

Transform existing object types into new ones by modifying their properties.

Conclusion

TypeScript's type system is both powerful and flexible, making it an essential tool for developers. While it may feel overwhelming at first, consistent practice and exploration will unlock its true potential. Dive into resources like TypeHero, experiment with these concepts in your projects, and explore the TypeScript documentation to deepen your understanding. The best way to master TypeScript is to start applying it—one step at a time!

Happy coding!


Top comments (0)