TypeScript Mapped Types |
Mapped Types are a powerful feature in TypeScript that allow creating new types based on existing ones.
It enables developers to batch modify property characteristics, such as making all properties optional or read-only.
Mapped types are the core technology behind TypeScript's built-in utility types.
Why Mapped Types Are Needed
In actual TypeScript development, we often need to create variants of existing types.
For example, we might want an optional version where all properties are optional, or a read-only version where all properties are read-only.
The traditional approach is to manually define these types, which is tedious and error-prone.
Mapped types provide a declarative way to automatically generate these type variants.
Concept Explanation: Mapped types use the
keyofandinkeywords to iterate through all keys of an existing type, then apply the same type transformation to each key.
Basic Mapped Types
Basic mapped types create new types by iterating over all keys of the original type.
This is the foundation for implementing utility types like Partial and Readonly.
Example
// Define user interface
interface User {
// User ID
id: number;
// Username
name: string;
// User email
email: string;
}
// Implement Partial utility type
// Iterate over all keys of T, add optional modifier ?
type Partial<T>={
// P iterates over all keys returned by keyof T
// T gets the value type of the corresponding key in the original type
?: T;
};
// Use Partial type
type PartialUser = Partial<User>;
// PartialUser type is equivalent to:
// { id?: number; name?: string; email?: string }
// Can provide only partial properties
var user: PartialUser ={ name:"Alice"};
console.log("Partial user: "+ JSON.stringify(user));
Output:
Partial user: {"name":"Alice"}
Syntax Explanation:
means iterate over all keys of type T. The?modifier makes the property optional.
Property Modifiers
Mapped types support various property modifiers to alter property characteristics.
These modifiers can be combined to achieve different type transformation needs.
Example
// Define user interface
interface User {
// Username
name: string;
// User age
age: number;
}
// Use readonly mapping: make all properties read-only
// Add readonly modifier
type Readonly<T>={
readonly : T;
};
// Use optional mapping: make all properties optional (basic version)
type Optional<T>={
?: T;
};
// Use -? mapping: remove optional modifier (make required)
// -? removes the existing ? modifier
type Required<T>={
-?: T;
};
// Test read-only type
var readonlyUser: Readonly<User>={ name:"Alice", age:25};
// readonlyUser.age = 30; // Error: read-only property cannot be modified
// Test optional type
var optionalUser: Optional<User>={ name:"Bob"};
console.log("Read-only: "+ JSON.stringify(readonlyUser));
console.log("Optional: "+ JSON.stringify(optionalUser));
Output:
Read-only: {"name":"Alice","age":25}Optional: {"name":"Bob"}
Modifier Explanation:
?adds optional,-?removes optional;readonlyadds read-only,-readonlyremoves read-only.
Key Name Mapping
Mapped types can also use the as keyword to remap key names.
This is very useful when you need to uniformly change key name formats.
Example
// Define user interface
interface User {
// User ID
id: number;
// Username
name: string;
// User age
age: number;
}
// Use as keyword to remap key names
// Add prefix to all keys
type WithPrefix<T, Prefix extends string>={
// Use template literal types to rename keys
// Capitalize capitalizes the first letter
[P in keyof T as `${Prefix}${Capitalize<string & P>}`]: T;
};
// Use WithPrefix to add "user" prefix
type PrefixedUser = WithPrefix<User,"user">;
// Transformed type:
// { userId: number; userName: string; userAge: number }
// Use prefixed type
var user: PrefixedUser ={ userId:1, userName:"Alice", userAge:25};
console.log("Prefixed: "+ JSON.stringify(user));
Output:
Prefixed: {"userId":1,"userName":"Alice","userAge":25}
Template Literal Types: Using
${Prefix}${Capitalize}can dynamically generate new key names.
Key Filtering
By combining conditional types with mapped types, key filtering can be achieved.
This is very useful when implementing utility types like Omit.
Example
// Define user interface
interface User {
// User ID
id: number;
// Username
name: string;
// User password
password: string;
// User email
email: string;
}
// Implement Omit: exclude specified keys
// Use conditional types to filter keys
type Omit<T, K extends keyof T>={
// P iterates over all keys of T
// If P can be assigned to K (i.e., in exclusion list), return never (exclude)
// Otherwise return P (keep the key)
[P in keyof T as P extends K ? never : P]: T;
};
// Use Omit to exclude password key
type UserWithoutPassword = Omit<User,"password">;
// Transformed type:
// { id: number; name: string; email: string }
// Use type after excluding password
var user: UserWithoutPassword ={ id:1, name:"Alice", email:"a@b.com"};
console.log("No password: "+ JSON.stringify(user));
Output:
No password: {"id":1,"name":"Alice","email":"a@b.com"}
Never Type: Using
neveras a property type in a mapped type completely removes that property.
Conditional Mapping
Mapped types can be combined with conditional types to apply different transformations based on property types.
This makes type transformations more flexible and intelligent.
Example
// Define API response interface
interface APIResponse {
// Response data
data: string;
// Error message
error: string;
// Is loading
isLoading:boolean;
// Timestamp
timestamp: number;
}
// Convert function types to () => void
// Iterate over all properties, conditionally transform based on type
type FunctionToVoid<T>={
// If T is a function type, convert to () => void
// Otherwise keep original type
: Textends(...args: any[])=> any
?()=>void
: T;
};
// Use conditional mapping
var response: FunctionToVoid<APIResponse>={
data:"hello",
error:"",
isLoading:false,
timestamp:Date.now()
};
console.log("Response: "+ JSON.stringify(response));
Output:
Response: {"data":"hello","error":"","isLoading":false,"timestamp":...}
Use Case: Conditional mapping is commonly used for handling API responses, cleaning configuration objects, and other scenarios requiring different processing based on types.
Built-in Mapped Types
TypeScript includes many utility types implemented using mapped types.
These utility types can meet most daily development needs.
Example
// Partial - make all properties optional
type P1 = Partial<{ a: string; b: number }>;
// Result: { a?: string; b?: number }
// Required - make all optional properties required
type R1 = Required<{ a?: string; b?: number }>;
// Result: { a: string; b: number }
// Readonly - make all properties read-only
type RO1 = Readonly<{ a: string; b: number }>;
// Result: { readonly a: string; readonly b: number }
// Pick - select specified properties
type PK = Pick<{ a: string; b: number; c:boolean},"a"|"b">;
// Result: { a: string; b: number }
// Omit - exclude specified properties
type OM = Omit<{ a: string; b: number; c:boolean},"c">;
// Result: { a: string; b: number }
// Test Partial
console.log("Partial: "+ JSON.stringify({} as P1));
// Test Pick
console.log("Pick: "+ JSON.stringify({ a:"x"} as PK));
Utility Type Composition: These built-in utility types are all implemented using mapped types and conditional types. Understanding their principles helps use them better.
Notes
- keyof keyword: Used to get a union type of all keys of a type
- in keyword: Used to iterate over a union of key names
- Modifier position:
?andreadonlybefore property names indicate adding modifiers - Negative modifiers:
-?and-readonlyare used to remove modifiers - as keyword: Used to remap key names, must return string or numeric literal types
Advanced: Mapped types can be combined with conditional types and template literal types to implement complex type transformations.
Summary
Mapped types are one of the most powerful features in TypeScript's type system.
- keyof: Get all keys of a type
- in: Iterate over key names for mapping
?: Add optional modifier- readonly: Add read-only modifier
- -?: Remove optional modifier
- as: Remap key names
Best Practice: Using mapped types effectively can significantly reduce repetitive type definitions and improve code maintainability.
YouTip