Creating Immutable Objects Safely in TypeScript
Exploring the key differences Between as const and Object.freeze in TypeScript
When developing an application, maintaining immutability plays a crucial role in enhancing code predictability. Immutable objects remain unchanged, preventing unintended state mutations and increasing the reliability of the application.
I would make an object immutable when requiring a constant, indicating a fixed value. TypeScript offers various methods to achieve immutability. In this article, we will explore the key differences between as const
and Object.freeze
which are common ways to ensure object immutability in TypeScript.
as const
as const
is a const assertion that converts variables to literal types. Literal types are the narrowest types. With const assertion, you can apply the same type inference rules to let
variables as you would with const
variables.
let apple = "Apple" as const; // Type is Apple
let apple = "Apple" // Type is string
const apple = "Apple"; // Type is Apple
const primary = 1; // Type is 1
Moreover, as const
not only converts the variable to a literal type, but also makes it read-only, prohibiting changes to the variable.
// Object
const person = {
name: "Suyeon",
job: "Developer"
} as const;
person.job = "Artist"; // Cannot assign to 'job' because it is a read-only property.
// Array
const persons = ["Suyeon", "John", "Hanna"] as const;
persons[0] = "Beautiful" // Cannot assign to '0' because it is a read-only property.
as const only makes variable read-only at compile time.
Since as const
is a TypeScript-specific feature that doesn't exist in vanilla JavaScript, it enforces read-only status at compile time. Consequently, when using as const
, being a dynamically typed language, can still infer or change the type of variables at runtime.
const person = {
name: "Suyeon",
job: "Developer"
} as const;
// After compile to Javascript
const person = {
name: "Suyeon",
job: "Developer"
};
as const makes all contents of nested objects immutable.
as const
extends its immutability to all contents of nested objects by fixing all properties as literal types, making them immutable.
const person = {
name: 'Suyeon',
info: {
job: 'Developer',
hobbies: ['Scuba Diving', 'Reading']
}
} as const;
person.info.job = 'Artist'; // Cannot assign to 'job' because it is a read-only property.
Object.freeze
Object.freeze
freezes an object. Freezing an object prevents extensions and makes existing properties non-writable and non-configurable. — MDN
const person = {
name: "Suyeon",
job: "Developer"
};
Object.freeze(person);
person.job = "Artist";
console.log(person); // {name: 'Suyeon', job: 'Developer'}
Object.freeze makes variable read-only both at compile time and runtime.
Object.freeze
ensures that a variable remains read-only both at compile time and runtime.
Object.freeze provides shallow immutability.
However, Object.freeze
provides shallow immutability, meaning it doesn’t make the values of nested objects immutable. For making complete immutable object, libraries like immer or immutable.js. may be necessary.
Wait a moment. What is Object.seal?
Object.seal
seals an object, preventing new properties from being added or removed, but allowing the values of existing properties to be modified.
const person = {
name: "Suyeon",
job: "Developer"
};
Object.seal(person);
delete job.person; // false
person.job = "Artist"; // {name: 'Suyeon', job: 'Artist'}
Conclusion
While as const
and Object.freeze
both seem to provide the same way to make an object immutable, there are subtle differences. Understanding their differences and limitations is valuable for making informed decisions in your codebase. Happy Coding!