
TypeScript, a statically typed language built on top of JavaScript, is well-known for its robust tooling, scalability, and efficient typing system. In this article, we're going to delve deep into some sophisticated TypeScript features, namely Const Assertions and Utility Types, and learn how they can be used to write safer, more flexible code.
Let's first understand the context with a snippet:
const socials = {
facebook: "https://facebook.com",
google: "https://google.com",
youtube: "https://youtube.com",
instagram: "https://instagram.com",
twitter: "https://twitter.com",
pinterest: "https://pinterest.com",
} as const;
This object socials holds URLs for various social media platforms. Notice the unusual as const clause. This is where our journey begins...
What is a Const Assertion?#
The as const syntax is called a const assertion. It's a way of telling TypeScript that the object being assigned is a constant and should not be changed. When applied to an object, it makes all of the object's properties read-only.
Diving Deeper with Utility Types#
Utility types in TypeScript provide convenient ways of type transformations, allowing developers to avoid unnecessary duplication.
To retrieve keys and values from our object, we use two utility types associated with our socials object:
type SocialTypes = typeof socials;
type SocialValues = SocialTypes[keyof typeof socials];
- typeof: The
typeofoperator retrieves the type of a variable. Here, we create a newSocialTypestype that shares the same structure as thesocialsobject. - keyof: The
keyofkeyword, when used with a type, produces a string or numeric literal union of its keys. - Indexed Access:
SocialTypes[keyof SocialTypes]is an indexed access type that returns the type of an object's values.
Generics: Creating Reusable and Type-Safe Functions#
Generics are tools for creating reusable components while preserving types. Let's create functions using generics to interact with our socials object:
const getSocialName = (
input: SocialTypes[keyof SocialTypes]
): keyof SocialTypes | undefined => {
return (Object.keys(socials) as (keyof SocialTypes)[]).find((key) => {
return socials[key] === input;
});
};
The function getSocialName will search the socials object and return the key corresponding to the URL passed as a parameter.
The challenge here is dealing with unknown keys and TypeScript's strict rules around indexing objects. We overcome this by typecasting Object.keys(socials) into an array of keyof SocialTypes, which tells TypeScript we're working with valid keys.
Similarly, we can define another generic function to get the URL value from the social name:
const getSocialValue = (input: keyof SocialTypes): SocialValues => {
return socials[input];
};
Conclusion#
Applying const assertions and utility types, TypeScript supports precise type manipulations without losing any integrity, creating smarter alternatives to JavaScript's flexibility. By incorporating these techniques, codebases become more flexible, robust, and easier to maintain — attributes every developer seeks.
TypeScript continues to evolve, perpetually adding features that enhance developer experience. Taking the time to understand these concepts will undoubtedly help you write better TypeScript code. Remember, in the world of software development, it's not just about solving problems — it's about doing so in the most efficient, scalable, and maintainable way possible. TypeScript is a powerful tool for achieving this goal.
Source: typescript-101 / day9