Rajat Kaushik
August 29, 2023
The introduction of TypeScript and its static typing capabilities has had a profound impact on the developer experience in the JavaScript world, making it now the industry-standard for most large-scale codebases.
However, TypeScript's value does not stop at static typing. It showcases its prowess through a collection of several utility types – predefined constructs that allow developers to seamlessly modify and utilize existing types: from for crafting optional properties to for inferring function returns, streamline complex type manipulations, saving developers both time and mental bandwidth.
In this article, we'll be talking about 12 underrated utility types that you should be using more in your codebase. These TypeScript utility types are like tools in your coding toolbox, each serving a unique purpose to help you shape your code with precision and clarity.
Table of Contents
Object Manipulation Types
- Partial<T>
- Required<T>
- Readonly<T>
- Mutable<T>
- Pick<T, Keys>
- Record<Keys, Value>
- Omit<T, Keys>
Union Manipulation Types
- Exclude<T, Excluded>
- NonNullable<T>
- Extract<T, Extracted>
Function Types
- Parameters<Function>
- Return Type<Function>
- Awaited<Function>
A note on interfaces vs types
We will be using both interfaces and types in this article.
In TypeScript, the choice between using types and interfaces depends on the specific scenario. Interfaces are suited for describing the shape of objects, defining their properties, methods, and events. On the other hand, types are more versatile, allowing you to define primitive type aliases, tuples, unions, and work with functions, complex types, and mapped types. Ultimately, the choice between types and interfaces depends on the specific use case and the developer's judgment.
1. Partial<T>
allows you to create a type where all properties of type are made optional. This is like transforming a strict "must-have" checklist into a more relaxed "choose what you want."
When it's useful: When dealing with forms or data that might not have all fields filled in.
2. Required<T>
enforces that all properties of type must be present. It's like making sure you have all the key ingredients for your favorite dish.
When it's useful: When you want to ensure certain properties are always included in your data.
3. Readonly<T>
creates a type where all properties of type are read-only. Think of it like a "no-touch" policy for your object properties.
When it's useful: When you want to prevent accidental changes to your data.
4. Pick<T, Keys>
helps you extract a type containing only the specified properties from type . Think of it as creating a custom mini-version of an object.
When it's useful: When you want to narrow down your object's properties to just the essentials.
5. Record<Keys, Value>
creates a type with specified keys of type and values of type . Imagine building your own dictionary of words and their meanings.
When it's useful: When you need to create a structured mapping between keys and values of specific type.
6. Omit<T, Keys>
creates a type by excluding specific properties listed in from type . It's like editing out parts of a picture you don't need.
When it's useful: When you want to remove certain properties from an object type.
7. Exclude<T, Excluded>
filters out specific types from a union that are assignable to the excluded type. It's like saying "I only want one type of snack."
When it's useful: When you want to narrow down the possible values of a union type.
8. NonNullable<T>
ensures a value is not or within a union. It's your way of saying "I want the real thing!"
When it's useful: When you need to ensure a value isn't missing in a union type.
9. Extract<T, Extracted>
retains only the types from a union that are assignable to the extracted type. It's like fishing out your favorite items from a mixed bag of snacks.
When it's useful: When you want to extract specific types from a union type.
10. Parameters<Function>
captures the parameter types of a function.
When it's useful: When you need to work with the types of function parameters.
11. ReturnType<Function>
captures the return type of a function. It's like knowing what you're getting as the final dish.
When it's useful: When you want to know what type of value a function will return.
12. Awaited<Function>
extracts the resolved type of a promise returned by a function. It's like getting the actual meal after waiting for your order.
When it's useful: When you're working with promises and want to know what they will resolve to.
Bonus: Mutable<T>
creates a type where all properties of type are mutable. TypeScript does not offer this type out-of-the-box, so we have to create it ourselves.
When it's useful: When you've got a read-only object but need to make changes to it.
Combination Example
Let's explore some examples by integrating various utility types in TypeScript. We'll adopt a theme of assembling a superhero team with diverse abilities. Using utility types, we'll model different facets of this scenario.
It's important to note that the use of and here is done explicitly to show you how they can be used together. TypeScript is usually smart enough to recognize the type of a promise's resolved value and apply that type inference on it's own.
This example demonstrates how you can leverage TypeScript utility types to model more complex scenarios involving asynchronous communication, asynchronous actions, and dynamic type analysis. It also adds an extra layer of fun and playfulness to our superhero team example! 🦸♂️🦸♀️🚀
Conclusion
TypeScript's utility types are built-in type manipulators that facilitate easy alterations and actions on established types. I hope you found these TypeScript utility types examples informative. This isn't the end; you can leverage these utility types to craft your own custom types. Using utility types can significantly enhance your code, making it more reusable, concise, and clean.
For more information about how to use Cosmic in your application, visit our documentation.
Want to get started with Cosmic? Create an account for free.