In this post you’ll learn how and when to use the Readonly
mapped type TypeScript.
Mapped types are a newer feature of TypeScript and act as a dynamic construct to create new types from existing ones.
The way that the Readonly
mapped type behaves is a little similar to how the readonly property on classes behaves, however much more powerful to transform entire data structures at once.
Let’s assume that we have a User
which can be created when signing up to a website, creating an account:
interface User {
id: string;
name: string;
email: string;
}
Once this user has been created, we do not want to modify any of those properties.
One option may be to use the readonly
modifier on each property:
interface ReadonlyUser {
readonly id: number;
readonly name: string;
readonly email: string;
}
Certainly this works, but can we do better? Yes, of course!
Adding readonly
to every single property is an overhead, but also let’s say we want to now change the initial User
interface, we’d have to also ensure that the ReadonlyUser
was changed as well. This opens up room for errors and mismatched type definitions.
So, what’s the better way? Mapped types!
A mapped type liked Readonly
will essentially “map” over our interface properties and change them. The Readonly
mapped type is exactly what we’re looking for to achieve automatic readonly
properties:
interface User {
id: string;
name: string;
email: string;
}
Once this user has been created, we do not want to modify any of those properties.
One option may be to use the readonly
modifier on each property:
// { readonly id: string; readonly name: string; readonly email: string; }
type ReadonlyUser = Readonly<User>;
Simple as that. Mapped types are an efficient way to reduce the code you write, whilst helping you to create even more type-safe code.
As a real-world example, I’d love to show you Object.freeze
(which is one of my favourite methods ever).
Here’s the TypeScript signature for Object.freeze
:
freeze<T>(o: T): Readonly<T>;
Recognise the Readonly
mapped type? This is super because TypeScript constructs these mapped types on-the-fly for us. So our data before it’s passed into Object.freeze
would be mutable and unfrozen, and as soon as we get it back from Object.freeze
it’ll automatically be marked as readonly
for every property.
Hiding mapped types away like this inside function calls is also fantastic, as it uses the generic type to infer our type down into the function and back out as frozen.
🏆 Check out my series of TypeScript Courses where you’ll learn TypeScript in-depth with real-world scenarios and examples to supercharge your JavaScript development.
So you’ve learned how to use the Readonly
mapped type in TypeScript, but also more importantly how to use it with generic types in our Object.freeze
example to let your data flow in and out and be mapped efficiently.
It’s also nice to not have to keep creating seemingly duplicated interface
logic that simply marks things as readonly
when a mapped type is fully sufficient.
But, if you need to just modify some properties on a class
or interface
, you’re probably best to go with a readonly property.
Happy mapping!