TypeScript provides various ways to define and manipulate object types and collections. One versatile utility type is Record, which facilitates the creation of an object type with a set of known properties. This guide explores the Record type in TypeScript, discussing its syntax, usage scenarios, differences with other data structures like Map, and techniques for iteration and recursion.
What is a Record in TypeScript?
A Record in TypeScript is a generic utility type that constructs an object type with a specific set of keys of a given type, and where all the values are of another specified type. The Record type is defined as Record<K, T>, where K represents the type of the keys and T represents the type of the values.
Basic syntax of Record
Here's a basic example of a Record:
type UserRecord = Record<string, number>const ages: UserRecord = {Alice: 25,Bob: 30}
In this example, UserRecord is a Record type where each key is a string and each value is a number.
When to use Record
Record is especially useful when you need to ensure all properties of an object have the same type, or when you want to map a fixed set of keys to values where the keys are known in advance but are too numerous or inconvenient to list individually in a type definition.
Comparing Record with Map in TypeScript
Both Record and Map store key-value pairs, but there are important differences:
- Type strictness:
Recordis more type-strict, enforcing all keys to be of one type and all values to be of another type.Map, however, can have mixed type keys and values if specified. - Iteration:
Maphas built-in methods for iteration, like.keys(),.values(), and.entries(), making it more suitable for cases where order of elements matters or when elements are frequently added and removed. - Serialization:
Recordobjects are just regular JavaScript objects and can be serialized directly usingJSON.stringify().Maprequires conversion to an array or another structure for serialization.
Example demonstrating the difference
const record: Record<string, number> = { Alice: 25, Bob: 30 }const map = new Map<string, number>([['Alice', 25],['Bob', 30]])console.log(JSON.stringify(record)) // {"Alice":25,"Bob":30}console.log(JSON.stringify(Array.from(map.entries()))) // [["Alice",25],["Bob",30]]
Iterating over a Record
To iterate over a Record, you can use Object class methods such as Object.keys(), Object.values(), and Object.entries(). Here's how you can loop over a Record:
const userAges: Record<string, number> = { Alice: 25, Bob: 30 }// Using Object.keys() to get all keysfor (const userName of Object.keys(userAges)) {console.log(userName)}// Using Object.values() to get all valuesfor (const age of Object.values(userAges)) {console.log(age)}// Using Object.entries() to get key-value pairsfor (const [userName, age] of Object.entries(userAges)) {console.log(`${userName} is ${age} years old`)}
Creating recursive Record types
Recursive Record types are useful for defining objects with nested structures of the same type. Here’s how you can define a recursive Record:
type RecursiveRecord = Record<string, number | RecursiveRecord>const nestedRecord: RecursiveRecord = {level1: {level2: {value: 10},anotherValue: 5}}function printRecord(record: RecursiveRecord) {for (const key in record) {if (typeof record[key] === 'object') {printRecord(record[key] as RecursiveRecord)} else {console.log(`${key}: ${record[key]}`)}}}printRecord(nestedRecord)
Best practices for using Record
- Use
Recordfor fixed schema objects: When the keys are known and uniform types are required,Recordis an excellent choice. - Consider using
Mapfor dynamic collections: If the collection grows dynamically and keys are not predetermined, aMapmight be more appropriate. - Leverage TypeScript’s type safety: Always specify the types for keys and values to fully leverage TypeScript's type-checking.
For further reading on records in TypeScript, see the official documentation.