One of the core components of a modern front-end framework is a responsive system. They are magic wands that enable applications to achieve high interactivity, dynamicity and responsiveness. Understanding what a responsive system is and how to apply it in practice is a crucial skill for every web developer.
Responsive systems are mechanisms that automatically keep the data source (model) and the data representation (view) layer in synchronization. Whenever the model changes, the view is re-rendered to reflect these changes. Let's take a simple Markdown editor as an example. It usually has two panes: one for writing Markdown code (modifying the underlying model), and the other for previewing compiled HTML (showing updated views). When you write content in the Writing pane, it is immediately and automatically previewed in the Preview pane. Of course, this is just a simple example. Often the situation is much more complicated.
In many cases, the data we want to display depends on some other data. In this case, the dependencies are tracked and the data is updated accordingly. For example, suppose we have a fullName property, which depends on the firstName and lastName properties. When any dependencies are modified, the fullName property is automatically recalculated and the results are displayed in the view.
Now that we have determined what responsiveness is, it is time to learn how the new Vue 3 responsive mechanism works and how to use it in practice. But before we do this, we'll take a quick look at the old Vue 2 responsive mechanism and its shortcomings.
Key Points
reactive
is for basic types and ref
is for objects. readonly
ref
Advanced responsive API methods such as reactive
and computed
Vue 3 resolves limitations found in Vue 2 responsive systems, such as detecting changes to array length and object properties added. watch
Behind the scenes, Vue 2 uses ES5 Object.defineProperty() to convert all properties of the data object to getter and setter. For each component instance, Vue creates a dependency observer instance. Any attributes collected/tracked as dependencies during component rendering will be recorded by the observer. Later, when the setter of the dependency is fired, the observer is notified that the component re-renders and updates the view. This is basically how all magic works. Unfortunately, there are some things to be aware of.
Vue cannot detect certain data changes due to Object.defineProperty() limitations. These include:
Luckily, to deal with these limitations, Vue provides us with the Vue.set API method, which adds a property to the responsive object, ensuring that the new property is also responsive, triggering view updates.
Let's explore the above situation in the following example:
<div id="app"> <h1>Hello! My name is {{ person.name }}. I'm {{ person.age }} years old.</h1> <button @click="addAgeProperty">Add "age" property</button> <p>Here are my favorite activities:</p> <ul> <li v-for="(item, index) in activities" :key="index">{{ item }} <button @click="editActivity(index)">Edit</button></li> </ul> <button @click="clearActivities">Clear the activities list</button> </div>
const App = new Vue({ el: '#app', data: { person: { name: "David" }, activities: [ "Reading books", "Listening music", "Watching TV" ] }, methods: { // 1. Add a new property to an object addAgeProperty() { this.person.age = 30 }, // 2. Setting an array item by index editActivity(index) { const newValue = prompt('Input a new value') if (newValue) { this.activities[index] = newValue } }, // 3. Modifying the length of the array clearActivities() { this.activities.length = 0 } } });
In the above example, we can see that none of these three methods work. We cannot add new properties to the person object. We cannot use indexes to edit items in the activities array. We also cannot modify the length of the activities array.
Of course, there are solutions to these situations, which we will explore in the next example:
const App = new Vue({ el: '#app', data: { person: { name: "David" }, activities: [ "Reading books", "Listening music", "Watching TV" ] }, methods: { // 1. Adding a new property to the object addAgeProperty() { Vue.set(this.person, 'age', 30) }, // 2. Setting an array item by index editActivity(index) { const newValue = prompt('Input a new value') if (newValue) { Vue.set(this.activities, index, newValue) } }, // 3. Modifying the length of the array clearActivities() { this.activities.splice(0) } } });
In this example, we use the Vue.set API method to add a new age property to the person object and select/modify a specific item from the activities array. In the last case, we only use the splice() array method built in JavaScript.
As we can see, this works, but it's a bit clumsy and leads to inconsistent code bases. Fortunately, in Vue 3, this problem has been resolved. Let's see how it's magical in the following example:
const App = { data() { return { person: { name: "David" }, activities: [ "Reading books", "Listening music", "Watching TV" ] } }, methods: { // 1. Adding a new property to the object addAgeProperty() { this.person.age = 30 }, // 2. Setting an array item by index editActivity(index) { const newValue = prompt('Input a new value') if (newValue) { this.activities[index] = newValue } }, // 3. Modifying the length of the array clearActivities() { this.activities.length = 0 } } } Vue.createApp(App).mount('#app')
In this example using Vue 3, we revert to the built-in JavaScript functionality used in the first example, and now all methods work properly.
In Vue 2.6, the Vue.observable() API method was introduced. It exposes a responsive system to some extent, allowing developers to explicitly make objects responsive. In fact, this is the same exact method Vue uses to wrap data objects internally, and is useful for creating minimal cross-component state storage for simple scenarios. But while it is useful, this single approach doesn't match the capabilities and flexibility of the full, feature-rich responsive API that comes with Vue 3. We will see the reason in the next section.
Note: Because Object.defineProperty() is just an ES5 feature and cannot be simulated, Vue 2 does not support IE8 and below.
The responsive system in Vue 3 has been completely rewritten to take advantage of the ES6 Proxy and Reflect API. The new version reveals a feature-rich responsive API that makes the system more flexible and powerful than before.
Proxy API allows developers to intercept and modify low-level object operations on target objects. A proxy is a clone/wrapper of an object (called a target) and provides special functions (called a trap) that respond to specific operations and overwrite the built-in behavior of JavaScript objects. If you still need to use the default behavior, you can use the corresponding Reflection API, which, as the name suggests, reflects the Proxy API's methods. Let's explore an example to see how these APIs are used in Vue 3:
<div id="app"> <h1>Hello! My name is {{ person.name }}. I'm {{ person.age }} years old.</h1> <button @click="addAgeProperty">Add "age" property</button> <p>Here are my favorite activities:</p> <ul> <li v-for="(item, index) in activities" :key="index">{{ item }} <button @click="editActivity(index)">Edit</button></li> </ul> <button @click="clearActivities">Clear the activities list</button> </div>
To create a new proxy, we use the new Proxy(target, handler) constructor. It accepts two parameters: the target object (person object) and the handler object, which defines which operations (get and set operations) will be intercepted. In the handler object, we use the get and set traps to track when properties are read and when properties are modified/added. We set up console statements to make sure the method works properly.
get and set traps take the following parameters:
Reflect API method accepts the same parameters as its corresponding proxy method. They are used to implement default behavior for a given operation, return attribute name for a get trap, and return true if a property is set, otherwise return false.
TheAnnotated track() and trigger() functions are Vue-specific and are used to track when properties are read and when properties are modified/added. As a result, Vue reruns the code using this property.
In the last part of the example, we use console statements to output the original person object. We then use another statement to read the attribute name of the proxy object. Next, we modify the age property and create a new hobby property. Finally, we output the person object again to see if it has been updated correctly.
This is a brief description of the Vue 3 responsive mechanism. Of course, the actual implementation is much more complex, but hopefully the examples provided above are enough to get you the main idea.
When using Vue 3 responsive mechanism, the following points need to be considered:
Finally, we go to the Vue 3 responsive API itself. In the following sections, we explore the API approach grouped logically. I grouped the methods because I think it's easier to remember to present in this way. Let's start with the basics.
The first group includes the most basic methods for controlling data responsiveness:
Let us now see the practical application of these methods:
<div id="app"> <h1>Hello! My name is {{ person.name }}. I'm {{ person.age }} years old.</h1> <button @click="addAgeProperty">Add "age" property</button> <p>Here are my favorite activities:</p> <ul> <li v-for="(item, index) in activities" :key="index">{{ item }} <button @click="editActivity(index)">Edit</button></li> </ul> <button @click="clearActivities">Clear the activities list</button> </div>
In this example, we explore the use of four basic responsive methods.
First, we create a counter ref object with a value of 0. Then, in the view, we place two buttons, incrementing and decreasing the counter's value, respectively. When we use these buttons, we see that counter is indeed responsive.
Secondly, we create a person responsive object. Then, in the view, we place two input controls that are used to edit the person's name and age. When we edit human properties, they are updated immediately.
Thirdly, we create a math read-only object. Then, in the view, we set a button to double the value of the PI property of math. But when we click the button, an error message will be displayed in the console telling us that the object is read-only and we cannot modify its properties.
Finally, we create an alphabetNumbers object that we don't want to convert to a proxy and mark it as the original object. It contains all letters and their corresponding numbers (for brevity, only the first three letters are used here). This order is unlikely to change, so we deliberately keep this object as a normal object, which is beneficial to performance. We render the object content in the table and set a button to change the value of the B attribute to 3. We do this to show that although the object can be modified, this does not cause the view to be rerendered.
markRaw is perfect for objects that we don't need to make it responsive, such as long lists of country codes, color names and their corresponding hexadecimal numbers, etc.Finally, we use the type checking method described in the next section to test and determine the type of each object we create. We use the onMounted() lifecycle hook to trigger these checks when the application is initially rendered.
Type checking method
The methods in this group are the "shallow" equivalents of ref, reactive, and readonly:
Let's understand these methods more easily by examining the following examples:
<div id="app"> <h1>Hello! My name is {{ person.name }}. I'm {{ person.age }} years old.</h1> <button @click="addAgeProperty">Add "age" property</button> <p>Here are my favorite activities:</p> <ul> <li v-for="(item, index) in activities" :key="index">{{ item }} <button @click="editActivity(index)">Edit</button></li> </ul> <button @click="clearActivities">Clear the activities list</button> </div>
This example starts with creating a settings shallow ref object. Then, in the view, we add two input controls to edit their width and height properties. But when we try to modify them, we see that they are not updated. To solve this problem, we add a button that changes the entire object and all its properties. Now it works. This is because the content of the value (width and height as a single property) is not converted to a responsive object, but the variation of the value (the entire object) is still tracked.
Next, we create a settingsA shallow reactive proxy that contains the width and height properties and a nested coords object containing the x and y properties. Then, in the view, we set an input control for each property. When we modify the width and height properties, we see that they are updated responsively. But when we try to modify the x and y properties, we see that they are not tracked.
Finally, we create a settingsB shallow readonly object that has the same properties as settingsA. Here, when we try to modify the width or height attribute, an error message appears in the console telling us that the object is read-only and we cannot modify its attributes. On the other hand, the x and y properties can be modified without any problem.
Nested coords objects from the last two examples are not affected by the transformation and remain as normal objects. This means it can be modified freely, but any modifications of it will not be tracked by Vue.
The next three methods are used to convert the proxy to ref(s) or normal objects:
Let's see how these conversions work in the following example:
const App = new Vue({ el: '#app', data: { person: { name: "David" }, activities: [ "Reading books", "Listening music", "Watching TV" ] }, methods: { // 1. Add a new property to an object addAgeProperty() { this.person.age = 30 }, // 2. Setting an array item by index editActivity(index) { const newValue = prompt('Input a new value') if (newValue) { this.activities[index] = newValue } }, // 3. Modifying the length of the array clearActivities() { this.activities.length = 0 } } });
In this example, we first create a basic person responsive object that we will use as the source object.
Then we convert the name property of person to a ref with the same name. Then, in the view, we add two input controls—one for name ref and another for person’s name property. When we modify one of them, the other will also be updated accordingly, so the responsive connection between them is maintained.
Next, we convert all properties of person to a single ref contained in the personDetails object. Then, in the view, we add two input controls again to test a ref we just created. As we can see, the age of personDetails is completely synchronized with the age property of person, just like in the previous example.
Finally, we convert the person responsive object to a rawPerson normal object. Then, in the view, we add an input control to edit the hobby property of rawPerson. But as we can see, Vue does not track converted objects.
The last set of methods is used to calculate complex values and "monitor" specific values:
Let's consider the following example:
<div id="app"> <h1>Hello! My name is {{ person.name }}. I'm {{ person.age }} years old.</h1> <button @click="addAgeProperty">Add "age" property</button> <p>Here are my favorite activities:</p> <ul> <li v-for="(item, index) in activities" :key="index">{{ item }} <button @click="editActivity(index)">Edit</button></li> </ul> <button @click="clearActivities">Clear the activities list</button> </div>
In this example, we create a fullName computed variable that is calculated based on firstName and lastName ref . Then, in the view, we add two input controls to edit the two parts of the full name. As we can see, when we modify any part, fullName will be recalculated and the results will be updated.
Next, we create a volume ref and set a watch effect for it. Every time the volume is modified, the effect will run the callback function. To prove this, in the view, we add a button that increments volume by 1. We set a condition in the callback function to test whether the value of volume can be divisible by 3. When it returns true, an alert message will be displayed. effect runs once when the application starts up, and sets the value of volume, and then runs again each time the value of volume is modified.
Finally, we create a state ref and set a watch function to track its changes. Once state changes, the callback function will be executed. In this example, we add a button to switch state between playing and paused. Every time this happens, an alert message is displayed.
watchEffect and watch look very similar in terms of functionality, but they have some obvious differences:
As you can see, the Vue 3 responsive API provides a wide range of methods that can be used in a variety of use cases. The API is very large, and in this tutorial, I only explore the basics. For more in-depth exploration, details, and edge cases, visit the responsive API documentation.
In this article, we introduce what a responsive system is and how it can be implemented in Vue 2 and Vue 3. We see that Vue 2 has some shortcomings successfully solved in Vue 3. The Vue 3 responsive mechanism is a complete rewrite based on modern JavaScript capabilities. Let's summarize its pros and cons.
Pros:
Disadvantages:
The bottom line is that the Vue 3 responsive mechanism is a flexible and powerful system that can be used by both Vue and non-Vue developers. No matter what your situation is, just grab it and start building something amazing.
Vue 3 Responsive System is a fundamental aspect of Vue.js, a popular JavaScript framework. It is responsible for tracking changes in the application state and updating the DOM (Document Object Model) to reflect these changes. The system is built around the concept of "responsive objects". When the properties of these objects change, Vue will automatically trigger the necessary updates. This makes it easier for developers to build dynamic, responsive web applications.
Vue 3's responsive system has been completely rewritten using JavaScript's Proxy object, providing a more efficient and powerful way to track changes compared to Vue 2's Object.defineProperty method. This new system allows Vue to track changes in nested properties, a limitation of Vue 2. It also reduces memory footprint and improves performance.
To use Vue 3 responsive system, you need to wrap your data using the reactive() function. This function makes the object and its properties responsive. When the property changes, Vue will automatically rerender components that depend on it. You can also use the ref() function to make a single variable responsive.
Vue 3 is used to create responsive references to values. It wraps the value in an object with a single property ".value" and makes this object responsive. This means that when the value changes, any component using this ref will be updated.
reactive() and ref() are both used to make data responsive in Vue 3, but they are used in different scenarios. The reactive() function is used to make an object responsive, while the ref() function is used to make a base value such as a string or a number responsive. However, ref() can also be used with objects, in which case it behaves similarly to reactive().
Vue 3 The responsiveness of arrays is the same as the ones that handle objects. If you use the reactive() function to make the array responsive, Vue tracks changes to the array elements and their length. This means that if you add, delete, or replace elements, Vue will update components that depend on that array.
The toRefs() function in Vue 3 is used to convert a responsive object to a normal object, where each property of the original object is represented as a ref. This is useful when you want to deconstruct a responsive object but still keep it responsive.
You can use the markRaw() function to prevent the object from being responsive. This is useful in some cases where you do not want Vue to track changes to the object.
The computed() function in Vue 3 is used to create a responsive property that depends on other responsive properties. The value of the computed property is automatically updated when any dependencies change. This is very useful for calculations or transformations that should be updated when the underlying data changes.
Vue 3's responsive system fully supports JavaScript's Map and Set data structures. If you make a Map or Set responsive, Vue tracks changes to its entries or elements, respectively. This means that if you add, delete, or replace an entry or element, Vue will update components that depend on Map or Set.
The above is the detailed content of Understanding the New Reactivity System in Vue 3. For more information, please follow other related articles on the PHP Chinese website!