Introduction: Harnessing the "Shallow" Power of Reactivity in Vue
Hello everyone, fellow explorers of Vue.js! We all know that reactivity is one of Vue's "magic spells," allowing UI to update instantly when data changes. Typically, we use ref() for single values and reactive() for complex objects, and Vue works its magic to track every nook and cranny of the data.
But sometimes, this "deepness" isn't always a good thing. With massive data structures or when integrating with external libraries, Vue's attempt to track everything can become a performance burden. That's when we need their "shallower" siblings: shallowRef and shallowReactive.
So, when is "shallow" actually "deep"? Let's find out!
ShallowRef: Only Cares About the Outer Shell
As the name suggests, shallowRef creates a "shallow" ref. What does this mean?
- It only tracks changes to the
.valueproperty of the ref itself. - It DOES NOT track changes within the object or array that
.valueholds.
When to use ShallowRef?
- Performance optimization with large, immutable objects: When you have a massive data object and you know you'll only replace the entire object (rather than modifying individual child properties),
shallowRefwill be much more efficient. Vue doesn't need to create proxies for all deeply nested properties, saving memory and CPU. - Integration with external libraries: If you're working with an object managed by another JavaScript library (e.g., a Three.js scene object, a D3.js chart), you might want Vue to only track when you assign a completely new object, avoiding conflicts or unnecessary proxying.
ShallowRef Example
Consider the following example:
<template> <p>User Name: {{ user.name }}</p> <p>User Age: {{ user.age }}</p> <button @click="changeUserName">Change User Name (Doesn't update)</button> <button @click="replaceUser">Replace User Object (Updates)</button></template><script setup>import { shallowRef, triggerRef } from 'vue';const user = shallowRef({ name: 'Alice', age: 30 });const changeUserName = () => { user.value.name = 'Bob'; // Modifying an inner property, DOES NOT trigger UI update console.log(user.value); // If you want to force an update, you can use triggerRef() // triggerRef(user);};const replaceUser = () => { user.value = { name: 'Charlie', age: 25 }; // Replacing the entire object, DOES trigger UI update console.log(user.value);};</script>In this example, clicking "Change User Name" will not update the UI because user.value itself hasn't been replaced. However, clicking "Replace User Object" will update the UI because you've assigned a new object to user.value.
ShallowReactive: Power at the Top-Level Properties
Similar to shallowRef, shallowReactive also creates a "shallow" reactive object.
- It only tracks changes to the top-level properties of the object.
- It DOES NOT track changes to deeply nested properties within child objects.
When to use ShallowReactive?
- Performance optimization for large objects with frequently changing top-level properties: If you have a complex object but you are only concerned with changing its direct properties (e.g., assigning a new child object to a top-level property),
shallowReactivewill be the appropriate choice. - Avoid unnecessary deep proxying: Helps conserve resources when there's no need to track every nested level of data.
ShallowReactive Example
Consider the following example:
<template> <p>User Info: {{ userInfo.name }} - {{ userInfo.details.city }}</p> <button @click="changeCity">Change City (Doesn't update)</button> <button @click="changeName">Change Name (Updates)</button> <button @click="replaceDetails">Replace Details (Updates)</button></template><script setup>import { shallowReactive } from 'vue';const userInfo = shallowReactive({ name: 'David', age: 40, details: { city: 'Hanoi', country: 'Vietnam' }});const changeCity = () => { userInfo.details.city = 'Ho Chi Minh'; // Modifying a deeply nested property, DOES NOT trigger UI update console.log(userInfo.details.city);};const changeName = () => { userInfo.name = 'Eve'; // Modifying a top-level property, DOES trigger UI update console.log(userInfo.name);};const replaceDetails = () => { userInfo.details = { city: 'London', country: 'UK' }; // Replacing a top-level property (details), DOES trigger UI update console.log(userInfo.details);};</script>Here, changing userInfo.details.city will not update the UI because it's a deeply nested property. However, changing userInfo.name or assigning a new object to userInfo.details (a top-level property) will trigger an update.
Conclusion: When is "Shallow" the Optimal Choice?
By default, you should always use ref and reactive because they provide the most comprehensive and safest reactivity. However, shallowRef and shallowReactive are powerful tools when you need tighter control over Vue's reactivity mechanism to:
- Optimize performance: With large, complex data structures that you frequently replace entirely or only modify top-level properties.
- Integrate with external libraries: When you want Vue to track at a specific level, to avoid conflicts or unnecessary proxying with objects managed by third-party libraries.
- Reduce memory and CPU overhead: By avoiding the creation of unnecessary deep proxies.
Remember, the choice between "shallow" and "deep" depends on your application's specific requirements and data structure. A clear understanding of each tool will help you build Vue applications that are not only powerful but also smooth and efficient!