Unraveling TypeScript’s Utility Types

Unraveling TypeScript’s Utility Types

Howdy, my dear Readers: Have you ever come across or heard of Utility types? If not, let’s explore this fantastic toolbox for writing great TypeScript code together, shall we?

In the world of JavaScript and TypeScript, developers seek code elegance and maintainability. Like skilled farmers choosing the best fruits, seasoned developers use TypeScript utility types as valuable tools. These utilities empower developers to refine and streamline their code with precision.

In this journey, we’ll demystify TypeScript Utility Types, revealing their role in crafting robust and enjoyable code. Meet our protagonist, Alvison Hunter, an imaginary character from Nicaragua — and yes, I’m the one behind this article, also known as Me!

The Ensemble of TypeScript Utility Types

So, technically speaking ,what the heck is a TS Utility Type? Well, In TypeScript, a Utility Type is a predefined generic type that provides a convenient way to operate on and manipulate other types. These utility types are like building blocks that help developers compose complex types with greater ease and readability. They often serve common patterns in type transformations and manipulations.

These utility types help improve code readability, reduce redundancy, and enhance type safety. By leveraging TypeScript Utility Types, developers can write more expressive and maintainable code, especially when dealing with complex type scenarios.

Let’s dive into the most common ones on this article, are you ready? If so, let’s go, dear reader!

Let’s start with Partial. The Partial utility type allows us to make all properties within a type optional. Perhaps we want to create a form where Alvison Hunter can update his details:

interface Developer {
  name?: string;
  country: string;
  language: string;
}


type UpdateDeveloperForm = Partial<Developer>;

const updateForm: UpdateDeveloperForm = {
  name: "Alvison Hunter Arnuero",
};

Now, let’s talk about Required. The Required utility type ensures that a specific property or a set of properties within a type are mandatory. Let's envision a scenario where Alvison Hunter is a distinguished developer(Not bragging about it, ok?), and we want to make sure his name is always present, just because he is also writing this article, is that clear?

interface Developer {
  name?: string;
  country: string;
  language: string;
}

const mandatoryDeveloper: Required<Developer> = {
  name: 'Alvison Hunter',
  country: 'Nicaragua',
  language: 'JavaScript',
};

What if we desire a more refined set of properties on the Developer interface? Well, in that case, let’s talk about Omit now. Conversely, the Omit utility type helps us create a type that excludes specific properties. Let's say we want a version of Alvison Hunter without the name, because we already use it too much on this article. We could do something like this:

interface Developer {
  name?: string;
  country: string;
  language: string;
}

type DeveloperWithoutName = Omit<Developer, 'name'>;

const devWithoutName: DeveloperWithoutName = {
  country: 'Nicaragua',
  language: 'JavaScript',
};

Alright, we’ve covered some foundational concepts, but let’s take it a step further. Is there a way for us to be more selective about the information we extract from the Developer interface? Can we handpick only the specific attributes that truly matter to us?

Well, yes, allow me to introduce you to our friend, Pick: The Pick utility type allows us to extract specific properties from a type. Suppose we want to cherry-pick Alvison Hunter's core attributes, just because we want to continue using that name:

interface Developer {
  name?: string;
  country: string;
  language: string;
}

type CoreAttributes = Pick<Developer, 'name' | 'country'> & { strongLanguage: string };

const DevCore: CoreAttributes = {
  name: 'Alvison Hunter',
  country: 'Nicaragua',
  strongLanguage: 'Python',
};

Absolutely! We’re making excellent progress — these tools are proving to be indispensable, resulting in a truly delightful outcome. Nevertheless, let’s take it a step further: what if we could fortify these properties, ensuring their immutability? Well, let’s talk about Readonly: In scenarios where immutability is key, the Readonly utility type ensures that the properties of a type cannot be modified:

interface Developer {
  name?: string;
  country: string;
  language: string;
}

const immutableAlvison: Readonly<Developer> = {
  name: 'Alvison Hunter',
  country: 'Nicaragua',
  language: 'JavaScript',
};

// Error: Cannot assign to 'name' because it is a read-only property.
immutableAlvison.name = 'Alvison Lucas Hudson';

Absolutely marvelous, but, let’s ponder a moment longer: What about the intricate symphony of object shapes in TypeScript? Don’t worry, my dear cherished Readers, for my unequivocal response is a resounding yes! Allow me to talk to you about Record: The Record utility type creates a mapped type where the keys are derived from a union of string literals. Imagine we want to create a team of developers with unique identifiers:

interface Developer {
  name?: string;
  country: string;
  language: string;
}

type DeveloperTeam = Record<'jsDev' | 'pyDev', Developer>;

const team: DeveloperTeam = {
  jsDev: { name: 'Alvison Hunter', country: 'Nicaragua', language:"JavaScript" },
  pyDev: { name: 'Declan Hunter', country: 'Unknown', language:"Python" },
};

Marvelous! The utility types indeed weave an enchanting tapestry in the world of TypeScript but how about function returning types such as object, string, boolean or number? Ok, in that case, let me present to you our dear beloved ReturnType: The ReturnType utility type unveils the return type of a function. Imagine Alvison Hunter, crafting a function that orchestrates a symphony of welcome messages personalized with a name. Picture this:

interface Developer {
  name: string;
  country: string;
  language: string;
}

type greetDevProp = Pick<Developer, "name">;

const generateWelcomeMessage = ({ name }: greetDevProp): string => {
  return `Welcome, ${name}!`;
};

const welcomeMessage: ReturnType<typeof generateWelcomeMessage> =
  "Welcome, Alvison";
console.log(generateWelcomeMessage({ name: "Alvison" }));

Ok, Supreme victory! We are loving this, but, how about getting types from Parameters, is that even possible? Well dear reader, it seems that we knew from the start of writing this article that you’d asked that, so, let me introduce you to the Parameters utility type. The Parameters utility type extracts the parameter types of a function. Suppose Alvison Hunter has a function that takes two parameters:

const greetDeveloper = (name: string, country: string): string => {
  return `Welcome, ${name} from ${country}!`;
}
const parameters: Parameters<typeof greetDeveloper> = ['Alvison', 'Nicaragua'];
console.log(parameters)

Lovely! What if we want to avoid null as params values? Ok, I got you, TypeScript has the NonNullable utility type for this case: When dealing with variables that could potentially be null or undefined, the NonNullable utility type ensures we are working with a value that is neither. Let's consider a case where Alvison Hunter's title could be null:

type DeveloperWithNullableTitle = {
  name: string;
  title: string | null;
  country: string;
};

type NoNullsProps<T> = {
  [K in keyof T]: NonNullable<T[K]>;
};

const nonNullableWebDevObj: NoNullsProps<DeveloperWithNullableTitle> = {
  name: 'Alvison Hunter',
  title: "Senior Web Developer",
  country: 'Nicaragua',
};

console.log(nonNullableWebDevObj)

Oh my gosh, this is truly remarkable! We’ve taken it up a notch by incorporating generics into this masterpiece. But hold on a second, let’s dive even deeper — what about harnessing the power of types within promises?
Well, Brace yourself for the next level of innovation and sophistication: Concluding our symphony, let me talk to you about Awaited: In an asynchronous world, the Awaited utility type fetches the type of a Promise's resolved value. Let’s Picture our dear Alvison Hunter fetching data from an API:

type PromiseReturnType = {
  name: string;
  title: string | null;
  country: string;
};

async function fetchData(): Promise<PromiseReturnType> {
  return {
    name: "Alvison Hunter",
    title: "Senior Web Developer",
    country: "Nicaragua",
  };
}

const fetchedData: Awaited<ReturnType<typeof fetchData>> = {
  name: "Alvison Hunter",
  title: "Senior Web Developer",
  country: "Nicaragua",
};

console.log(fetchedData);

Well, my dear readers, as you have seen and learned on this article, utility types have emerged as more than just tools; they are the virtuosos of code composition, allowing developers to paint with clarity and precision. As we strolled through the orchard, gathering pearls of wisdom from TypeScript’s utility types, consider this not just a journey but an invitation to delve deeper into the subtleties that make these tools truly exceptional.

Just as a skilled artisan meticulously selects pearls and apples, these utility types are the treasures you carefully choose to elevate your development experience. They act as the brushstrokes that render your code resilient, expressive, and, dare I say, delightful.

So, fellow developer, let’s continue this odyssey and unveil the full potential of TypeScript’s utility types. Your codebase, like a well-tuned instrument, will harmonize in gratitude. Even in the fantastical coding realm where Alvison Hunter crafts these narratives, his nod of approval echoes through the imaginary corridors.

We sincerely hope this article has been an enriching chapter in your coding journey, much like the joy Alvison Hunter experienced while penning it for you, folks. Happy coding, and may your TypeScript endeavors continue to rock!