Learn in 5 Minutes: Understanding Immutability, Shallow and Deep Comparisons & Their Effect on Reactivity
While learning state management in React, I realized that understanding immutability is essential. Without grasping what immutability is and how it impacts state management, you can’t fully appreciate React’s reactivity. This led me to write this article. If you’re a detail junkie like me, you’ll love diving into this topic!
What is Reactivity?
In frontend development, reactivity refers to detecting changes in the data and updating the DOM accordingly. Different JavaScript frameworks and libraries follow various strategies to handle reactivity efficiently. Immutability helps us detect changes more effectively.
What is the Immutability Principle and Why Do We Practice It?
Immutability means that once a data structure is created, it should not be mutated. Instead, a newer version of it should be created and the reference of the data would be changed this way.
This principle helps us avoid side effects, simplifies change detection, and enhances performance.
A Quick Primer on Data Types and Equality in JavaScript
In JavaScript, primitive data types hold values and are inherently immutable, while non-primitive data types (like objects and arrays) hold references.
Checking if a primitive value has changed is straightforward — just compare the values.
For non-primitive values, things get trickier, and immutability becomes crucial. When dealing with objects and arrays, since they hold references, both loose equality (==) and strict equality (===) operators compare references, not values. This means that even if two objects look the same, they are considered different if their references are not the same. Essentially, these operators check references to determine equality or changes.
console.log(5 == '5'); // true (type coercion: string '5' is converted to number 5)
console.log(5 === '5'); // false (different types: number vs string)
const obj1 = { name: "Ahmet" };
const obj2 = { name: "Ahmet" };
const obj3 = obj1; // Same reference as obj1
console.log(obj1 == obj2); // false (different references)
console.log(obj1 === obj2); // false (different references)
console.log(obj1 == obj3); // true (same reference)
console.log(obj1 === obj3); // true (same reference)
Shallow Comparison
You might wonder why a beginner-level topic like data types and equality checks is included in an advanced article about immutability and reactivity. The answer is simple: Shallow Comparison is an advanced evolution of equality operators for non-primitive data types. It checks references between previous and next versions of the data and inspects top-level elements to see if they have changed. Inspecting top-level elements means:
const user = { name: 'Ahmet', address: { city: 'Sakarya' } };
// Mutable way: Changing `name` and deleting `address` property
// Change the `name` property
user.name = 'Ali';
// Delete the `address` property
delete user.address;
In this example, the shallow comparison still would be able to detect that user object is changed.
But it wouldn’t be able to notice changes in nested properties like this:
const user = { name: 'Ahmet', address: { city: 'Sakarya' } };
// Change the nested `city` property
user.address.city= 'Istanbul';
And when React would check if the userobject is changed with shallow comparison, it wouldn’t be able to notice the change and wouldn’t be able to provide reactivity and re-render the component.
Recommended by LinkedIn
Deep Comparison vs. Immutability
Deep comparison checks properties recursively till the bottom and finds nested changes too but it’s more complex and less performant. This is where immutability proves its worth.
By adhering to the immutability principle, we ensure that whether our data changes are shallow or nested, we don’t mutate the original object. Instead, we create a new object, which changes the reference and allows shallow comparison to detect changes. This approach maintains data consistency, ensures predictable application behavior, and facilitates easy change tracking.
Immutable Way:
let user = { name: 'Ahmet', address: { city: 'Sakarya' } };
user = {
...user,
name: 'Ali',
address: {
...user.address,
city: 'Istanbul'
}
}; // Creating a new object and reassigning
Approaches of React vs. Vue in Terms of Immutability and Reactivity
In React, immutability is a core principle for state management. We follow the immutability principle in every kind of state management. For example, in class components, we use setState; in functional components, we use the useState hook. Reducers in Redux also handle state with immutability.
In Vue, we aren’t forced to practice immutability. Instead, Vue allows us to mutate data directly. To maintain reactivity without enforcing immutability, Vue employs different strategies:
By leveraging these techniques, Vue provides flexibility in handling reactivity without enforcing immutability, which contrasts with React’s more rigid approach to immutability.
The differences between React and Vue extend beyond their approaches to immutability and reactivity. In general, React tends to provide developers with a set of manual tools and utilities, giving them more control but often requiring more configuration and decision-making. On the other hand, Vue integrates many tools and features directly into the framework, aiming to simplify the development process with built-in solutions.
For example, by default in React, if a parent component re-renders, its children also re-render, even if their props or state have not changed. To optimize performance and prevent unnecessary re-renders, React offers tools like React.memo, shouldComponentUpdate, useCallback, which allows developers to specify prop dependencies for child components, preventing re-renders if those dependencies do not change.
In contrast, Vue’s reactivity system handles this optimization by default. Vue efficiently tracks and updates only the components with changed properties, and reduces the need for manual optimizations. If you want to handle this manually, you can define your reactive data with shallowReactive and your data would be only checked with shallow comparison and it won’t create a deep watcher to catch nested changes.
Both frameworks have their strengths and trade-offs. React’s strict immutability and shallow comparison model offer predictability and performance but require developers to manage state changes meticulously. Vue’s more permissive approach with automatic reactivity handling, offers ease of use and integrated optimizations, though it might not always enforce immutability.
Conclusion
In summary, practicing immutability may dictate how we update our data but in return, it grants us some big benefits:
For more on immutability and state management in React, check out my article series on my React learning process. The next part (Part 3) will focus on state management in React. ✋