DEV Community

Cover image for Unraveling TypeScript’s Utility Types
Alvison Hunter Arnuero | Front-End Web Developer
Alvison Hunter Arnuero | Front-End Web Developer

Posted on • Edited on

Unraveling TypeScript’s Utility Types

Howdy, dear Readers: Have you ever wondered about TypeScript's 'Utility types'? These tools are a treasure chest for supercharging your TypeScript code. Let's explore how they enhance your programming experience.

In the world of JavaScript and TypeScript, developers often seek code elegance and maintainability. TypeScript Utility Types, like skilled farmers selecting ripe fruits, empower developers to refine and streamline their code, fostering a sense of craftsmanship.

Join me, Alvison Hunter, a software enthusiast from Nicaragua, as we unravel the mysteries of TypeScript Utility Types by giving you some code example of each of these magnificent tools. Are you ready to dive in?

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.

Utility Types Code Examples:

Let’s start with Partial. The Partial utility type allows us to make all properties within a type optional. Let's create a new Backend Developer instance but using the existing Developer interface, using some of its keys:

interface FrontendDeveloper {
  name?: string;
  country?: string;
  languages?: string[];
}

type BackendDeveloper = Partial<FrontendDeveloper>;

const RequestedDeveloper: BackendDeveloper = {
  name: "Alvison Hunter",
};

console.log(RequestedDeveloper);
Enter fullscreen mode Exit fullscreen mode

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 all of his information is always present, just because he is also writing this article, is that clear?

interface SoftwareEngineer {
  name?: string;
  stack: string;
  languages: string[];
}

const NewSoftwareEng: Required<SoftwareEngineer> = {
  name: 'Alvison Hunter',
  stack: 'MERN',
  languages: ['JavaScript', 'TypeScript', 'Python', 'Golang'],
};

console.log(NewSoftwareEng);
Enter fullscreen mode Exit fullscreen mode

What if we desire a more refined set of properties on the on a new PythonDeveloper 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 this PythonDeveloper interface but without the name, because we already use it too much on this article. We could do something like this:

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

type PythonDevWithoutCountry= Omit<PythonDeveloper, 'country'>;

const newPythonDev: PythonDevWithoutCountry = {
  name: 'Declan Hunter',
  language: 'Python',
};

console.log(newPythonDev);
Enter fullscreen mode Exit fullscreen mode

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 JavaDeveloper {
  name?: string;
  country: string;
  language: string;
}

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

const newJavaDeveloper: CoreAttributes = {
  name: 'Francisco Membreno',
  country: 'Nicaragua',
  strongLanguage: 'Java',
};

console.log(newJavaDeveloper);
Enter fullscreen mode Exit fullscreen mode

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 GolangDeveloper {
  name?: string;
  country: string;
  language: string;
}

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

// Error: Cannot assign to 'name' because it is a read-only property.
newGolangDev.name = 'Alvison Lucas Hudson';
console.log(newGolangDev);
Enter fullscreen mode Exit fullscreen mode

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 RubyDeveloper {
  name?: string;
  country: string;
  language: string;
}

type RubyDevsTeam = Record<'jsDev' | 'pyDev' | 'rbDev', RubyDeveloper>;

const newRubyDevTeam: RubyDevsTeam = {
  jsDev: { name: 'Alvison Hunter', country: 'Nicaragua', language:"JavaScript" },
  pyDev: { name: 'Declan Hunter', country: 'Unknown', language:"Python" },
  rbDev: { name: 'Liam Hunter', country: 'Unknown', language:"Ruby" },
};

console.log(newRubyDevTeam);
Enter fullscreen mode Exit fullscreen mode

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 WordPressDeveloper {
  name: string;
  pluginDev: boolean;
  blockEditor: string;
}

type greetWordPressDevProps = Pick<WordPressDeveloper, "name">;

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

const TwelcomeMessage: ReturnType<typeof generateWelcomeMessage>= "Alvison";
console.log(typeof TwelcomeMessage);
Enter fullscreen mode Exit fullscreen mode

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

type ParametersType = Parameters<typeof greetDeveloper>;

const fnWithRetrievedParams = (params: ParametersType) => {
  const [name, country] = params;
  return [name, country];
}

console.log(fnWithRetrievedParams(["Alvison", "Nicaragua"]));
Enter fullscreen mode Exit fullscreen mode

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 NoNullsKeysProps<T> = {
  [K in keyof T]: NonNullable<T[K]>;
};

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

console.log(nonNullableWebDevObj);
Enter fullscreen mode Exit fullscreen mode

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 write a function that helps us fetch data from a fake API:

type TPromiseReturnType = {
  name: string;
  title: string;
  country: string;
};

const fakeResponse = {
  name: "Alvison Hunter",
  title: "Senior Web Developer",
  country: "Nicaragua",
};

async function fetchData(): Promise<TPromiseReturnType> {
  return fakeResponse;
}

type TfetchedDataReturnType = Awaited<ReturnType<typeof fetchData>>;

// Use an async function to await the fetchData promise
async function getFetchedData() {
  let fetchedData: TfetchedDataReturnType = await fetchData();
  console.log(fetchedData);
}

getFetchedData();

Enter fullscreen mode Exit fullscreen mode

Well, dear readers, as we've seen, utility types are more than just tools; they're the virtuosos of code composition, enabling developers to write with clarity and precision. Think of this journey through TypeScript’s utility types as an invitation to explore the nuances that make these tools exceptional.

So, as we close this chapter, remember that this is just the beginning. The world of TypeScript’s utility types is vast and full of potential. Keep exploring, keep experimenting, and let your curiosity guide you. With each new discovery, you’ll find yourself becoming more adept at wielding these powerful tools, turning everyday coding tasks into opportunities for creative expression.

Thank you for joining me on this journey. I hope this article has enriched your coding adventure as much as I enjoyed writing it. Until next time, keep coding, keep creating, and keep pushing the boundaries of what's possible.

❤️ Enjoyed the article? Your feedback fuels more content.
💬 Share your thoughts in a comment.
🔖 No time to read now? Well, Bookmark for later.
🔗 If it helped, pass it on, dude!

Top comments (2)

Collapse
 
josetorres_1 profile image
José Antonio Torres

Good article

Collapse
 
alvisonhunter profile image
Alvison Hunter Arnuero | Front-End Web Developer

Thanks Pal!