TypeScript Working with Mapped Types
TypeScript is a statically typed superset of JavaScript that brings a powerful type system to the JavaScript ecosystem. One of the advanced and useful features in TypeScript is mapped types. Mapped types allow developers to create new types by transforming each property in an existing type. This is extremely valuable when you need to perform operations like making all properties optional, readonly, or transforming their types based on some rules. In this blog post, we will explore the fundamental concepts of working with mapped types in TypeScript, their usage methods, common practices, and best practices.
Table of Contents
- Fundamental Concepts of Mapped Types
- Usage Methods
- Common Practices
- Best Practices
- Conclusion
- References
Fundamental Concepts of Mapped Types
At its core, a mapped type in TypeScript uses a property signature with a for...in - like syntax to iterate over the properties of an existing type and create a new type. The basic syntax of a mapped type is as follows:
type MappedType<OriginalType> = {
[Property in keyof OriginalType]: NewPropertyType;
};
OriginalTypeis the type whose properties we want to iterate over.Propertyis a variable that represents each property name in theOriginalTypeduring the iteration.keyof OriginalTypeis a type query that returns a union of all property names in theOriginalType.NewPropertyTypeis the type that we assign to each property in the new type.
Let’s look at a simple example:
// Original type
type User = {
name: string;
age: number;
};
// Mapped type to make all properties optional
type OptionalUser = {
[Property in keyof User]?: User[Property];
};
const optionalUser: OptionalUser = {
name: 'John'
};
In this example, we create a new type OptionalUser from the User type. The OptionalUser type has the same properties as User, but all of them are optional.
Usage Methods
Making Properties Optional or Readonly
We can use mapped types to make all properties in a type optional or readonly.
// Original type
type Person = {
firstName: string;
lastName: string;
age: number;
};
// Make all properties optional
type OptionalPerson = {
[Property in keyof Person]?: Person[Property];
};
// Make all properties readonly
type ReadonlyPerson = {
readonly [Property in keyof Person]: Person[Property];
};
const optionalPerson: OptionalPerson = {
firstName: 'Jane'
};
const readonlyPerson: ReadonlyPerson = {
firstName: 'Bob',
lastName: 'Smith',
age: 30
};
// This will cause a compilation error because the property is readonly
// readonlyPerson.age = 31;
Transforming Property Types
We can also transform the types of properties in a type. For example, we can convert all string properties to numbers.
type StringToNumber<Type> = {
[Property in keyof Type]: Type[Property] extends string ? number : Type[Property];
};
type Example = {
name: string;
age: number;
};
type TransformedExample = StringToNumber<Example>;
const transformed: TransformedExample = {
name: 123,
age: 25
};
Common Practices
Creating Partial and Readonly Utility Types
TypeScript already provides built - in utility types Partial<T> and Readonly<T> which are implemented using mapped types.
type Car = {
make: string;
model: string;
year: number;
};
// Using Partial<T>
type PartialCar = Partial<Car>;
const partialCar: PartialCar = {
make: 'Toyota'
};
// Using Readonly<T>
type ReadonlyCar = Readonly<Car>;
const readonlyCar: ReadonlyCar = {
make: 'Honda',
model: 'Civic',
year: 2022
};
Filtering Properties
We can use mapped types to filter properties based on their types.
type OnlyStringProperties<Type> = {
[Property in keyof Type as Type[Property] extends string ? Property : never]: Type[Property];
};
type MixedType = {
name: string;
age: number;
address: string;
};
type StringProperties = OnlyStringProperties<MixedType>;
const stringProps: StringProperties = {
name: 'Alice',
address: '123 Main St'
};
Best Practices
Keep Mapped Types Simple and Readable
Mapped types can become complex quickly, especially when using conditional types and type queries. It’s important to keep the code simple and readable. If a mapped type becomes too complicated, consider breaking it down into smaller, more manageable types.
Use Built - in Utility Types
TypeScript provides several built - in utility types like Partial<T>, Readonly<T>, Pick<T, K>, and Omit<T, K> that are implemented using mapped types. Use these utility types whenever possible to avoid reinventing the wheel.
Document Mapped Types
If you create custom mapped types, document them clearly. Explain what the type does, what the input and output types are, and any assumptions or limitations.
Conclusion
Mapped types in TypeScript are a powerful feature that allows developers to create new types by transforming existing types. They can be used to make properties optional or readonly, transform property types, and filter properties. By understanding the fundamental concepts, usage methods, common practices, and best practices of mapped types, developers can write more robust and maintainable TypeScript code.