TypeScript - Utility Types

Hello there, future coding wizards! Today, we're going to embark on an exciting journey through the magical realm of TypeScript Utility Types. Don't worry if you're new to programming; I'll be your friendly guide, and we'll explore these concepts together, step by step. So, grab your virtual wands (keyboards), and let's dive in!

TypeScript - Utility Types

What are Utility Types?

Before we start, let's understand what Utility Types are. Imagine you have a toolbox filled with various tools. Each tool helps you perform a specific task more efficiently. That's exactly what Utility Types are in TypeScript - they're pre-built tools that help us manipulate and transform types easily.

Now, let's look at each of these magical tools one by one!

Partial Type in TypeScript

The Partial type is like a spell that makes all properties in an object optional. It's super useful when you want to create an object where you don't need to specify all properties.

Let's see it in action:

interface Wizard {
  name: string;
  age: number;
  house: string;
}

function updateWizard(wizard: Wizard, fieldsToUpdate: Partial<Wizard>) {
  return { ...wizard, ...fieldsToUpdate };
}

const harryPotter: Wizard = {
  name: "Harry Potter",
  age: 11,
  house: "Gryffindor"
};

const updatedHarry = updateWizard(harryPotter, { age: 17 });
console.log(updatedHarry);
// Output: { name: "Harry Potter", age: 17, house: "Gryffindor" }

In this example, Partial<Wizard> allows us to update only the age of Harry without needing to specify all other properties. It's like waving a wand and saying, "Partial Revelio!"

Required Type in TypeScript

The Required type is the opposite of Partial. It's like casting a spell that makes all properties in an object required, even if they were originally optional.

interface MagicalCreature {
  name: string;
  power?: string;
  age?: number;
}

const dragon: Required<MagicalCreature> = {
  name: "Norwegian Ridgeback",
  power: "Fire Breath",
  age: 2
};

// This would cause an error:
// const unicorn: Required<MagicalCreature> = {
//   name: "Silver Horn"
// };

Here, even though power and age were optional in the original interface, the Required type makes them mandatory. It's like saying, "Accio all properties!"

Pick Type in TypeScript

The Pick type allows you to create a new type by selecting specific properties from an existing type. It's like using a Summoning Charm to call forth only the properties you need.

interface Potion {
  name: string;
  ingredients: string[];
  brewingTime: number;
  effect: string;
}

type PotionLabel = Pick<Potion, 'name' | 'effect'>;

const polyjuicePotion: PotionLabel = {
  name: "Polyjuice Potion",
  effect: "Transforms the drinker into another person"
};

In this example, we've created a new type PotionLabel that only includes the name and effect properties from the Potion interface. It's perfect for when you need just a few specific details!

Omit Type in TypeScript

The Omit type is the opposite of Pick. It creates a new type by removing specific properties from an existing type. Think of it as using the Vanishing Spell on certain properties!

interface SpellBook {
  title: string;
  author: string;
  pages: number;
  secretSpell: string;
}

type PublicSpellBook = Omit<SpellBook, 'secretSpell'>;

const beginnerSpellBook: PublicSpellBook = {
  title: "Standard Book of Spells, Grade 1",
  author: "Miranda Goshawk",
  pages: 250
};

Here, we've created a PublicSpellBook type that includes all properties of SpellBook except for secretSpell. It's like saying, "Show me everything but the secret!"

Readonly Type in TypeScript

The Readonly type is like casting a protection spell on your properties. It makes all properties in a type read-only, preventing accidental modifications.

interface Wand {
  wood: string;
  core: string;
  length: number;
}

const harryWand: Readonly<Wand> = {
  wood: "Holly",
  core: "Phoenix feather",
  length: 11
};

// This would cause an error:
// harryWand.length = 12;

With Readonly, we've ensured that once a wand is created, its properties can't be changed. It's like putting an unbreakable charm on your objects!

ReturnType Type in TypeScript

The ReturnType utility type allows us to extract the return type of a function. It's like using Legilimency to peek into a function and see what it returns!

function castSpell(spellName: string): { name: string, power: number } {
  // Spell casting logic here
  return { name: spellName, power: Math.random() * 100 };
}

type SpellResult = ReturnType<typeof castSpell>;

const lumos: SpellResult = {
  name: "Lumos",
  power: 50
};

In this example, SpellResult is inferred as { name: string, power: number }, which is the return type of castSpell. It's incredibly useful when working with complex functions!

Record Type in TypeScript

The Record type is a powerful spell that creates an object type with a specific key type and value type. It's like conjuring a magical map where you define what the keys and values should be.

type HouseCup = Record<string, number>;

const housePoints: HouseCup = {
  "Gryffindor": 472,
  "Hufflepuff": 352,
  "Ravenclaw": 426,
  "Slytherin": 472
};

Here, HouseCup is a type where the keys are strings (house names) and the values are numbers (points). It ensures that our house points object has the correct structure.

NonNullable Type in TypeScript

The NonNullable type is like casting a spell to banish null and undefined values. It creates a new type by excluding null and undefined from a given type.

type MagicalItem = string | number | null | undefined;

type DefiniteMagicalItem = NonNullable<MagicalItem>;

const definiteItem: DefiniteMagicalItem = "Invisibility Cloak";
// This would cause an error:
// const nullItem: DefiniteMagicalItem = null;

In this example, DefiniteMagicalItem is a type that can be either string or number, but not null or undefined. It's perfect when you want to ensure you're working with actual values!

Utility Types Cheat Sheet

Here's a quick reference table for all the Utility Types we've covered:

Utility Type Description Example
Partial Makes all properties in T optional Partial<Wizard>
Required Makes all properties in T required Required<MagicalCreature>
Pick<T, K> Creates a type with only the properties K from T Pick<Potion, 'name' | 'effect'>
Omit<T, K> Creates a type without the properties K from T Omit<SpellBook, 'secretSpell'>
Readonly Makes all properties in T read-only Readonly<Wand>
ReturnType Extracts the return type of a function type T ReturnType<typeof castSpell>
Record<K, T> Creates an object type with keys of type K and values of type T Record<string, number>
NonNullable Creates a type by excluding null and undefined from T NonNullable<MagicalItem>

And there you have it, young wizards! You've now mastered the basic spells of TypeScript Utility Types. Remember, like any magic, these types become more powerful with practice. So, keep experimenting, and soon you'll be casting these types as effortlessly as saying "Wingardium Leviosa"!

Credits: Image by storyset