TypeScript is a powerful superset of JavaScript that brings static typing to the dynamic world of web development. While many developers are familiar with the basics, there are hidden gems within TypeScript that often go unnoticed. One such gem is the as const feature, which proves to be incredibly useful in various situations.
Table of contents
Open Table of contents
What is as const
?
At its core, as const
is a feature that makes objects immutable in TypeScript. Let’s delve into an example to understand its significance.
Suppose we have an object named fruits
:
const fruits = {
apple: "red",
banana: "yellow",
orange: "orange",
};
By default, TypeScript infers the types of the properties in fruits as strings. Now, imagine a situation where we want to create a function that accepts any of these fruits as an argument. Without as const, we would encounter redundancy and potential maintenance challenges.
function eatFruit(fruit: "apple" | "banana" | "orange") {
// Function logic here
}
Here, we have duplicated the fruit values, introducing a source of truth discrepancy. If the fruits object is modified, we need to remember to update this function as well.
The Magic of as const
Enter as const. By applying this modifier to the fruits object, TypeScript treats it as a read-only object, preventing any modifications. This not only enhances code robustness but also improves type inference.
const fruits = {
apple: "red",
banana: "yellow",
orange: "orange",
} as const;
Now, attempting to modify any property of fruits will result in a compilation error. TypeScript knows that these values are constants and enforces immutability.
fruits.apple = "green";
// Cannot assign to 'apple' because it is a read-only property.
What about Object.freeze
?
Indeed, Object.freeze can be used to achieve a similar effect as as const in terms of making objects immutable. However, there are some distinctions between the two approaches, both in terms of behavior and usage.
Object.freeze
:
-
Runtime Impact: Object.freeze operates at runtime, preventing modifications to an object during the program’s execution. This means that attempts to modify the object will result in errors at runtime.
-
Limited Type-Level Impact: While Object.freeze enforces runtime immutability, it doesn’t provide the same level of type-level information to TypeScript as as const. TypeScript might not fully understand the frozen nature of the object when it comes to type inference.
-
Shallow Freezing: Object.freeze operates shallowly, meaning it only freezes the top level of the object. If the object contains nested objects, those nested objects are not automatically frozen.
const deepFruits = {
apple: { color: "red" },
banana: { color: "yellow" },
orange: { color: "orange" },
};
Object.freeze(deepFruits);
// Attempting to modify the nested object is allowed
deepFruits.apple.color = "green"; // No error at runtime
as const
:
-
Compile-time Impact: as const operates at compile time, providing TypeScript with more information about the immutability of the object. This leads to better type inference and stricter type checking during development.
-
Enhanced Type Inference: With as const, TypeScript infers literal types for the properties, ensuring that you get the exact types of the values specified in the object.
-
Deep Immutability: as const provides deep immutability, meaning it automatically applies immutability not only to the top level but also to all nested objects within the structure.
const deepFruits = {
apple: { color: "red" },
banana: { color: "yellow" },
orange: { color: "orange" },
} as const;
// TypeScript catches the error during compilation
deepFruits.apple.color = "green"; // Compilation error
- Automatic Propagation: With as const, the immutability constraints automatically propagate through nested structures, ensuring a consistent and comprehensive immutability across the entire object.
Type Extraction with typeof
and keyof
The real magic of as const becomes apparent when combined with typeof
and keyof
. Suppose we want to create a function that dynamically accepts any of the fruits without hardcoding them. We can achieve this by extracting the type and keys of the fruits object.
type FruitKeys = keyof typeof fruits;
The FruitKeys
type now represents the union of all keys in the fruits
object. This enables us to create a more dynamic eatFruit
function:
function eatFruit(fruit: FruitKeys) {
// Function logic here
}
This improvement ensures that our function automatically adapts to changes in the fruits
object, eliminating the need for manual updates.
Conclusion
In conclusion, the as const
feature in TypeScript is a hidden gem that enhances code quality, promotes immutability, and enables dynamic typing. By combining it with typeof
and keyof
, developers can create more flexible and resilient code. Next time you find yourself working with constants or objects with fixed properties, consider unleashing the power of as const to elevate your TypeScript experience.