As developers, we often encounter a common problem when coding in JavaScript: if there’s a bug, JavaScript doesn’t always show or throw errors during development. However, when we move to the production environment, those errors suddenly appear, making debugging a headache and consuming a lot of time.
To address this issue, Microsoft introduced TypeScript, a superset of JavaScript that adds optional static typing to the language. TypeScript enables strict type checking in your code, which can help catch certain types of errors at compile time. Learning TypeScript has been incredibly worthwhile for me. Understanding advanced topics not only improves your code-writing skills but also saves you significant time. TypeScript catches errors at compile time, reducing effort spent on debugging.
In this article, you’ll learn about the following:
What is TypeScript?
- An introduction to TypeScript and its benefits.
Why is TypeScript Important?
- Understanding the significance of using TypeScript in your projects.
Exploring Advanced TypeScript Keywords:
- We’ll dive into advanced TypeScript concepts such as
readonly
,record
,pick
,partial
,exclude
, andmap
. You’ll see how these keywords are used in real-life codebases.
- We’ll dive into advanced TypeScript concepts such as
Wrapping Up and Additional Resources:
What is TypeScript❓
TypeScript is a strongly typed programming language that builds on JavaScript, giving you better tooling at any scale. This means that TypeScript adds syntax on top of JavaScript, allowing developers to add types.
Why you Should use Typescript in your Next Project?
Compile-Time Errors: Typescript is Detect Error at compile time JavaScript’s inability to support types and compile-time error checks means it’s not a good fit for server-side code in complex and large codebases. and the reason to use TypeScript is that it detects compilation errors during development, making runtime errors unlikely. It incorporates static typing, helping a programmer check type correctness at compile time.
Runs Everywhere: so, typescript compiles every version of JavaScript including latest version of Es2022 and others like ES6, ES5, and ES3. You can use it with the latest framework like Nextjs , Expressjs , React etc.
Static Typing: besides helping you catch bugs static typing gives the code more structure and ensures it is self-documented. This is because the type of information makes it easier to understand how classes, functions, and other structures work. It also becomes easier to refactor code
Exploring Advanced TypeScript Keywords 🔽
Pick
The Pick
utility type in TypeScript allows you to create a new type from an existing model by selecting some of its properties. This is incredibly useful when you want to focus on a subset of properties from a larger type, particularly when passing data to functions or components that do not need to work with the entire object.
Pick<Type, Keys>
Type
is the original type you are selecting properties from.Keys
is a union of property names fromType
that you want to include in the new type.
Example
Suppose you have a User
type with multiple properties, but you need a function that only requires a few properties of the User
type User = {
id: number;
name: string;
email: string;
age: number;
};
// Creating a type that picks only 'name' and 'email' from User
type UserContactInfo = Pick<User, 'name' | 'email'>;
function sendEmail(user: UserContactInfo) {
console.log(`Sending email to ${user.name} at ${user.email}`);
}
// This function will only accept name and email properties of a User
In this example, Pick<User, 'name' | 'email'>
creates a new type UserContactInfo
that includes only the name
and email
properties from the User
type. This makes sendEmail
more flexible by only requiring the information it needs, reducing the risk of unnecessary data being passed around.
Real-world Use Case
Pick
is particularly useful in scenarios where only specific data is relevant, enhancing code readability and maintainability. For instance, when interacting with a database or an external API, you might only want to expose certain properties of your internal models to ensure data privacy and security. By using Pick
, you can easily create more restrictive types that serve these purposes without modifying the original models.
In frontend development, Pick
can be used to pass only the necessary data to components, avoiding over-fetching or exposing sensitive information. This practice can help in optimizing performance and maintaining security by ensuring that components have access only to the data they need to function.
Read Only
The Readonly
utility type in TypeScript is used to make all properties of a given type read-only. This means that once an object is created with this type, its properties cannot be reassigned. The Readonly
utility is particularly useful when you want to ensure that an object remains unchanged throughout your application.
Readonly<T>
T
is the type you want to make immutable.
Example
Consider a scenario where you have a User
type and you want to create a user object that should not be modified after creation:
type User = {
id: number;
name: string;
};
const user: Readonly<User> = {
id: 1,
name: "John Doe",
};
// Trying to modify the object will result in a TypeScript error
user.name = "Jane Doe"; // Error: Cannot assign to 'name' because it is a read-only property.
In this example, Readonly<User>
makes the id
and name
properties of the User
object immutable. Any attempt to modify the properties of user
will result in a compile-time error, ensuring the object's integrity throughout the program.
Real-world Use Case
The Readonly
utility type is useful in a variety of scenarios, especially when working with configurations, constants, or any data that should not change after initialization. For example, when you're passing an object to a function or component and you want to ensure that this object is not modified by that function or component, Readonly
can enforce this constraint.
In frontend frameworks, such as React, Readonly
can be used in props to make sure that components do not modify the props they receive, adhering to the principle of immutability. This can help prevent bugs related to unintended mutations and makes the data flow in your application more predictable.
Partial
The Partial
utility type in TypeScript makes all properties of a given type optional. This is incredibly useful in scenarios where you want to update or modify parts of an object without the need to provide values for every property. By converting all properties of the type to optional, Partial
facilitates the creation of objects that can have any combination of properties from the given type, making it perfect for patching or updating objects.
Partial<T>
T
is the type you want to make partially optional.
Example
Consider you have a User
type with several properties, and you're implementing a function to update user information. You might not require all properties to perform an update—only the ones provided by the user. Here's how you could use Partial
to achieve this:
type User = {
id: number;
name: string;
email: string;
age?: number;
};
function updateUser(id: number, changes: Partial<User>) {
// Imagine this function updates the user with the given id
// in the database using the changes object
}
// Updating user with id 1, changing only the email
updateUser(1, { email: 'newemail@example.com' });
In this example, Partial<User>
makes all properties of User
optional, allowing the updateUser
function to accept an object with any subset of User
properties. This means you can update a user with just their email, name, age, or any combination thereof, without needing to provide the entire user object.
Real-world Use Case
Partial
is especially useful in situations where you're dealing with forms or any kind of data update mechanism in your applications. When users are allowed to update parts of their information without requiring them to fill out every field, Partial
types can ensure type safety and flexibility in your functions that handle these updates.
Furthermore, in state management libraries or scenarios where you might want to partially update the state, Partial
can help in defining reducers or update functions that accept partial state objects. This help in creating more readable and maintainable code, as the intention and capabilities of such functions are made clear through the use of Partial
.
Record
The Record
utility type in TypeScript is a generic type that helps to construct an object type with a set of properties of a given type. It's defined as Record<K, T>
where K
represents the type of the keys and T
the type of the values. This utility is particularly useful for creating objects where you know the shape of keys but want all values to have a specific type. It ensures type safety in dynamic data structures where you might not know the exact names of the properties at development time but do know the structure of the object.
Example
Imagine you're developing an application that manages user permissions for different sections of the app. Each user can have a different set of permissions for various parts of the application, such as "read", "write", "admin", etc. You want to ensure that each section's permissions are correctly typed to avoid bugs related to incorrect permission handling.
type Role = "admin" | "user" | "guest";
type Permission = "read" | "write" | "put" | "delete";
// Mapping each role to its permissions
const rolePermissions: Record<Role, Permission[]> = {
admin: ["read", "write", "put", "delete"],
user: ["read", "write"],
guest: ["read"],
};
function checkRolePermission(role: Role, permission: Permission): boolean {
const permissions = rolePermissions[role];
return permissions.includes(permission);
}
// Usage examples
if (checkRolePermission("admin", "delete")) {
console.log("Admin has delete permission.");
}
if (!checkRolePermission("guest", "write")) {
console.log("Guest does not have write permission.");
}
In this example, rolePermissions
is an object where each key is a Role
and each value is an array of Permission
. The Record<Role, Permission[]>
type explicitly defines that each role (admin, user, guest) is associated with a specific array of permissions. This ensures that any operation involving rolePermissions
respects the defined types, reducing the risk of runtime errors due to incorrect permission assignments
This pattern is particularly useful in applications with complex access control logic, allowing developers to easily manage and check permissions across different parts of the application based on the user's role. It simplifies the maintenance and extension of the permissions model, as adding or modifying roles and permissions can be done in a single, centralized location.
Exclude
The Exclude
utility type in TypeScript is used to construct a type by excluding from a union type all members that are assignable to some other type. Essentially, it allows you to create a new type by removing certain types from an existing one. This is particularly useful when you want to narrow down types or prevent specific types from being used.
Here's the syntax for Exclude
:
Exclude<T, U>
T
is the union type you start with.U
is the type or types you want to exclude fromT
.
Example
Imagine you have a union type representing various events in an application, but you want to create a type that represents only the events that are not system events. Here's how you could use Exclude
to achieve this:
type AllEvents = 'click' | 'scroll' | 'keypress' | 'systemStart' | 'systemShutdown';
type UserEvents = Exclude<AllEvents, 'systemStart' | 'systemShutdown'>;
// Now, UserEvents is 'click' | 'scroll' | 'keypress'
In this example, Exclude
is used to create a UserEvents
type by excluding 'systemStart'
and 'systemShutdown'
from the AllEvents
type. As a result, UserEvents
is limited to 'click'
, 'scroll'
, and 'keypress'
.
Real-world Use Case
Exclude
is particularly useful in scenarios where you're dealing with libraries or frameworks that emit various types of events, and you want to differentiate between user-generated events and system-generated events. By excluding system events, you can create handlers that are strictly typed to only user events, improving code safety and readability.
For instance, if you're working with a component library in a UI framework and you want to add event listeners for user interactions but ignore system events, you could use Exclude
to ensure your event listeners are only attached to the types of events you care about. This prevents accidentally attaching listeners to system events and makes your intent clear to anyone reading your code.
Exclude
is a powerful tool in TypeScript for creating more precise types, leading to safer and more maintainable code by ensuring that variables and parameters are exactly the types they need to be, without the types you want to avoid.
Map
In TypeScript, a Map
object holds key-value pairs where the keys can be any value (including functions, objects, or any primitive). The use of Map
over plain objects or other types of key-value stores provides several benefits, including better performance for large sets of data, keys are not limited to strings or symbols (allowing for richer data structures), and maps remember the original insertion order of the keys.
Example
Here's how you can use a Map
in a scenario similar to the previous example, where we manage user roles and their permissions:
type Role = "admin" | "user" | "guest";
type Permission = "read" | "write" | "put" | "delete";
// Creating a map of roles to permissions
const rolePermissions = new Map<Role, Permission[]>([
["admin", ["read", "write", "put", "delete"]],
["user", ["read", "write"]],
["guest", ["read"]],
]);
function checkRolePermission(role: Role, permission: Permission): boolean {
const permissions = rolePermissions.get(role) || [];
return permissions.includes(permission);
}
// Usage examples
if (checkRolePermission("admin", "delete")) {
console.log("Admin has delete permission.");
}
if (!checkRolePermission("guest", "write")) {
console.log("Guest does not have write permission.");
}
In this example, rolePermissions
is a Map
where each key is a Role
and its corresponding value is an array of Permission
. This structure is similar to the previous example using Record
, but with the added benefits of a Map
. One of the key advantages here is the flexibility of Map
keys, which can be objects or any other type, not just strings or symbols. This is particularly useful in scenarios where you might need to use complex keys. It also provides several utility methods such as .size
, .delete()
, and .has()
, which can be more intuitive to use than object properties when managing collections of data.
Conclusion
Thank you for reading my article! If you find it helpful or interesting, please consider sharing it with your developer friends. For more content like this, don't forget to follow me on Hash node.
If you're interested in learning more about TypeScript and other web development topics, consider subscribing to my blog or newsletter. You'll receive updates whenever I publish new articles.
I'd love to hear your experiences with TypeScript. Feel free to share your thoughts or any interesting use cases in the comments section below.
Connect with me on Twitter Github Linkedin
Thank you for Reading :)