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:
Record
is 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:
Map
has 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:
Record
objects are just regular JavaScript objects and can be serialized directly usingJSON.stringify()
.Map
requires 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
Record
for fixed schema objects: When the keys are known and uniform types are required,Record
is an excellent choice. - Consider using
Map
for dynamic collections: If the collection grows dynamically and keys are not predetermined, aMap
might 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.