TypeScript is a powerful superset of JavaScript that introduces static typing and advanced type features to enhance developer productivity and code reliability. While basic types and interfaces in TypeScript provide a solid foundation, there are advanced type features like keyof
, mapped types, and conditional types that allow for even more expressive and flexible type definitions. In this article, we’ll explore these advanced TypeScript types and demonstrate how they can be utilized effectively in your code.
1. Understanding keyof
The keyof
operator in TypeScript allows you to extract the keys of an object type. It returns a union of string literal types representing the keys present in the given type. Let’s see an example:
type Person = {
name: string;
age: number;
address: string;
};
type PersonKeys = keyof Person; // "name" | "age" | "address"
In the above example, PersonKeys
is a type that represents the keys "name"
, "age"
, and "address"
from the Person
type. This is particularly useful when you want to create generic functions or utilities that operate on object properties dynamically.
2. Working with Mapped Types
Mapped types in TypeScript allow you to transform or manipulate existing types to create new ones. They provide a concise way to iterate over the properties of an object type and make modifications based on a pattern. Let’s explore a couple of examples:
Example 1: Readonly properties
type ReadonlyPerson = {
readonly [K in keyof Person]: Person[K];
};
const person: ReadonlyPerson = {
name: "John",
age: 25,
address: "123 Main St"
};
person.name = "Jane"; // Error: Cannot assign to 'name' because it is a read-only property.
In the above example, we create a new type ReadonlyPerson
using a mapped type. By using the readonly
modifier and keyof Person
, we ensure that all properties of ReadonlyPerson
are readonly, mirroring the properties of the original Person
type.
Example 2: Partial properties
type PartialPerson = {
[K in keyof Person]?: Person[K];
};
const partialPerson: PartialPerson = {
name: "John"
};
Here, we define a PartialPerson
type using a mapped type. The ?
operator makes all properties optional, allowing us to create an object with only a subset of properties from the original Person
type.
3. Exploring Conditional Types
Conditional types in TypeScript allow you to create type definitions that depend on a condition. They enable you to define different types based on the properties or relationships of other types. Let’s see an example:
type Filter<T, U> = T extends U ? T : never;
type StringOrNumber = string | number;
type OnlyNumbers = Filter<StringOrNumber, number>; // number
In the above example, the Filter
type takes two generic parameters T
and U
. It checks if T
extends U
and returns T
if true, otherwise never
. In this case, Filter<StringOrNumber, number>
evaluates to number
, effectively filtering out any non-numeric types.
4. Advanced Techniques with Advanced Types
Now that we have explored the individual advanced TypeScript types, let’s look at how they can be combined to achieve more complex type definitions. Here are a couple of examples:
Example 1: Indexing into a mapped type
type PersonInfo<T extends keyof Person
> = {
label: T;
value: Person[T];
};
function getProperty<T extends keyof Person>(prop: T): PersonInfo<T> {
return {
label: prop,
value: person[prop]
};
}
const nameInfo = getProperty("name");
console.log(nameInfo.value); // "John"
In this example, we define a PersonInfo
type that takes a generic type T
, which extends keyof Person
. It represents information about a specific property of Person
, including the property name (label
) and its corresponding value (value
). The getProperty
function retrieves the property value based on the provided key and returns an object of PersonInfo
type. We then obtain the name information by calling getProperty("name")
, and access the value using nameInfo.value
.
Example 2: Extracting subset of properties
type ExtractProperties<T, U> = {
[K in keyof T]: K extends U ? T[K] : never;
};
type PersonSubset = ExtractProperties<Person, "name" | "age">;
In this example, we create a ExtractProperties
type that takes two generic parameters T
and U
. It constructs a new type by iterating over the properties of T
using a mapped type. If the property key extends U
, it includes the property in the resulting type; otherwise, it assigns never
to that property. The PersonSubset
type extracts a subset of properties from Person
by specifying the desired keys "name"
and "age"
.
Conclusion
Advanced TypeScript types like keyof
, mapped types, and conditional types provide powerful capabilities to create expressive and precise type definitions. By leveraging these features, you can enhance the type safety and maintainability of your codebase. Whether it’s extracting keys, transforming types, or conditionally defining types, these advanced TypeScript types empower you to handle complex scenarios with ease. Take advantage of these techniques and unlock the full potential of TypeScript in your projects.
There we have how to Use Advanced TypeScript Types: keyof, Mapped Types, Conditional Types, if you want more like this be sure to check out some of my other posts!