Bricktowntom is committed to exceeding your expectations by providing a positive, enjoyable experience while visiting my site. Thank you for considering Bricktowntom's Market Place. I appreciate the trust you have placed in me!!
Understanding the New Reactivity System in Vue 3

Understanding the New Reactivity System in Vue 3


Understanding the New Reactivity System in Vue 3

Reactivity systems are one of the key parts of modern front-end frameworks. They’re the magic wand which makes apps highly interactive, dynamic, and responsive. Understanding what a reactivity system is and how it can be applied in practice is a crucial skill for every web developer.

A reactivity system is a mechanism which automatically keeps in sync a data source (model) with a data representation (view) layer. Every time the model changes, the view is re-rendered to reflect the changes.

Let’s take a simple Markdown editor as an example. It usually has two panes: one for writing the Markdown code (which modifies the underlying model), and one for previewing the compiled HTML (which shows the updated view). When you write something in the writing pane, it’s immediately and automatically previewed in the previewing pane. Of course, this is just a simple example. Often things are far more complex.

In many cases, the data we want to display depends on some other data. In such a scenario, the dependencies are tracked and the data is updated accordingly. For example, let’s say we have a fullName property, which depends on firstName and lastName properties. When any of its dependencies are modified, the fullName property is automatically re-evaluated and the result is displayed in the view.

Now that we’ve established what reactivity is, it’s time to learn how the new Vue 3 reactivity works, and how we can use it in practice. But before we do this, we’ll take a quick look at the old Vue 2 reactivity and its caveats.

A Brief Exploration of Vue 2 Reactivity

Reactivity in Vue 2 is more or less “hidden”. Whatever we put in the data object, Vue makes it reactive implicitly. On the one hand, this makes the developer’s job easier, but on the other hand it leads to less flexibility.

Behind the scenes, Vue 2 uses the ES5 Object.defineProperty() to convert all of the data object’s properties into getters and setters. For each component instance, Vue creates a dependencies watcher instance. Any properties collected/tracked as dependencies during the component’s render are recorded by the watcher. Later on, when a dependency’s setter is triggered, the watcher is notified and the component re-renders and updates the view. This is basically how all the magic works. Unfortunately, there are some caveats.

Change Detection Caveats

Because of the limitations of Object.defineProperty(), there are some data changes that Vue can’t detect. These include:

  • adding/removing a property to/from an object (such as obj.newKey = value)
  • setting array items by index (such as arr[index] = newValue)
  • modifying the length of an array (such as arr.length = newLength)

Fortunately, to deal with these limitations Vue provides us with the Vue.set API method, which adds a property to a reactive object, ensuring the new property is also reactive and thus triggers view updates.

Let’s explore the above cases 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 
    }
  }
});

Here’s a CodePen example.

In the above example, we can see that none of the three methods is working. We can’t add a new property to the person object. We can’t edit an item from the activities array by using its index. And we can’t modify the length of the activities array.

Of course, there are workarounds for these cases and we’ll explore them 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)
    }
  }
});

Here’s a CodePen example.

In this example, we use the Vue.set API method to add the new age property to the person object and to select/modify a particular item from the activities array. In the last case, we just use the JavaScript built-in splice() array method.

As we can see, this works, but it’s a bit hacky and leads to inconsistency in the codebase. Fortunately, in Vue 3 this has been resolved. Let’s see the magic in action, 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')

Here’s a CodePen example.

In this example, which uses Vue 3, we revert to the built-in JavaScript functionality, used in the first example, and now all methods work like a charm.

In Vue 2.6, a Vue.observable() API method was introduced. It exposes, to some extent, the reactivity system allowing developers to make objects reactive explicitly. Actually, this is the exact same method Vue uses internally to wrap the data object and is useful for creating a minimal, cross-component state store for simple scenarios. But despite its usefulness, this single method can’t match the power and flexibility of the full, feature-rich reactivity API which ships with Vue 3. And we’ll see why in the next sections.

Note: because Object.defineProperty() is an ES5-only and un-shimmable feature, Vue 2 doesn’t support IE8 and below.

How Vue 3 Reactivity Works

The reactivity system in Vue 3 was completely rewritten in order to take advantage of the ES6 Proxy and Reflect APIs. The new version exposes a feature-rich reactivity API which makes the system far more flexible and powerful than before.

The Proxy API allows developers to intercept and modify low-level object operations on a target object. A proxy is a clone/wrapper of an object (called target) and offers special functions (called traps), which respond to specific operations and override the built-in behavior of JavaScript objects. If you still need to use the default behavior, you can use the corresponding Reflection API, whose methods, as the name suggests, reflect those of the Proxy API. Let’s explore an example to see how these APIs are used in Vue 3:

let person = {
  name: "David",
  age: 27
};

const handler = {
  get(target, property, receiver) {
    // track(target, property)
    console.log(property) // output: name
    return Reflect.get(target, property, receiver)
  },
  set(target, property, value, receiver) {
    // trigger(target, property)
    console.log(`${property}: ${value}`) // output: "age: 30" and "hobby: Programming"
    return Reflect.set(target, property, value, receiver)
  }
}

let proxy = new Proxy(person, handler);   

console.log(person)

// get (reading a property value)
console.log(proxy.name)  // output: David

// set (writing to a property)
proxy.age = 30;

// set (creating a new property)
proxy.hobby = "Programming";

console.log(person) 

Here’s a CodePen example.

To create a new proxy, we use the new Proxy(target, handler) constructor. It takes two arguments: the target object (person object) and the handler object, which defines which operations will be intercepted (get and set operations). In the handler object, we use the get and set traps to track when a property is read and when a property is modified/added. We set console statements to ensure that the methods work correctly.

The get and set traps take the following arguments:

  • target: the target object which is wrapped by the proxy
  • property: the property name
  • value: the property value (this argument is used only for set operations)
  • receiver: the object on which the operation takes place (usually the proxy)

The Reflect API methods accepts the same arguments as their corresponding proxy methods. They’re used to implement the default behavior for the given operations, which for the get trap is returning the property name and for the set trap is returning true if the property was set or false if not.

The commented track() and trigger() functions are specific to Vue and are used to track when a property is read and when a property is modified/added. As a result, Vue re-runs the code that’s using that property.

In the last part of the example, we use a console statement to output the original person object. Then we use another statement to read the property 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 that it has been updated correctly.

And this is how Vue 3 reactivity works in a nutshell. Of course, the real implementation is way more complex, but hopefully the example presented above is enough for you to grasp the main idea.

There’s also a couple of considerations when you use Vue 3 reactivity:

  • it only works on browsers supporting ES6+
  • the reactive proxy isn’t equal to the original object

Continue reading
Understanding the New Reactivity System in Vue 3
on SitePoint.

Source: Site Point