Reactivity in Vue 3

Dec 11, 2020

Vue 3 has a new reactivity system based on ES6 proxies. The new reactivity system overlaps with the much-debated Composition API because the Composition API lets you use Vue's reactivity system without using Vue components at all. Some people have even talked about using Vue as a backend framework. Here's how it works.

Using ref()

Vue has a global ref() function that creates a reactive wrapper around a JavaScript primitive. For example, here's how you can create a "reactive" counter object.

const { ref } = require('vue');

const count = ref(0);

// RefImpl { _rawValue: 0, _shallow: false, __v_isRef: true, _value: 0 }
console.log(count);

++count.value;

// RefImpl { _rawValue: 1, _shallow: false, __v_isRef: true, _value: 1 }
console.log(count);

What's interesting about this ref? Using Vue's global watchEffect() function, you can watch for updates to ref.

const { ref, watchEffect } = require('vue');

const count = ref(0);

watchEffect(function handler() { console.log(count.value); });

// Prints "1" because Vue knows to call `handler()` whenever count changes
++count.value;

Vue is smart enough to understand ref() values returned from setup(), so you can define reactive state without defining a data property. For example, even though the counter component doesn't have a data property, it still reacts to updates to the value of count because count is a ref.

const { createApp, ref } = require('vue');

const app = createApp({
  template: '<counter></counter>'
});

app.component('counter', {
  // Clicking the button increments the counter, because Vue is smart enough
  // to understand reactive properties returned from `setup()`
  template: `
    <div>
      <h1>{{count}}</h1>
      <button v-on:click="++count">Increment</button>
    </div>
  `,
  setup: function() {
    const count = ref(0);
    return { count };
  }
});

Using reactive()

Vue 3 also introduces a reactive() function that behaves like ref(), but for objects. Remember that ref() generally should only be used on primitive values: numbers, strings, booleans, BigInts, and symbols.

The reactive() function adds reactivity to an object's properties. Call reactive() on an object, and you get back a proxied object that you can use with watchEffect(). For example, because character is reactive in the below example, watchEffect() will print out the character's name every time it changes.

const { reactive, watchEffect } = require('vue');

const character = reactive({ name: 'Jean-Luc Picard' });

watchEffect(() => { console.log(character.name); });

// Prints "Locutus of Borg"
character.name = 'Locutus of Borg';

The biggest improvement with reactive() versus Vue 2's data property is that reactive() can listen for when you create new properties, not just access existing ones. In the below example, watchEffect() is smart enough to pick up when you create a new property age on character.

const { reactive, watchEffect } = require('vue');

const character = reactive({ name: 'Jean-Luc Picard' });

watchEffect(() => { console.log(character.age); });

// Prints "59"
character.age = 59;

One gotcha with reactive(): it debounces changes that happen on the same tick of the event loop. The below code will print "61" and "62", it will not print "59" or "60" because those changes happen synchronously before "61".

const { reactive, watchEffect } = require('vue');

const character = reactive({ name: 'Jean-Luc Picard' });

watchEffect(() => { console.log(character.age); });

// Prints "61"
character.age = 59;
character.age = 60;
character.age = 61;

// Prints "62"
setImmediate(() => { character.age = 62; });

If you need to return an object property from setup(), you should use reactive(). For example, if instead of having a simple count, you have an article with a property pageViews that you want to increment, you should wrap the article object in reactive().

app.component('counter', {
  template: `
    <div>
      <h1>{{article.title}}: {{article.pageViews}} Page Views</h1>
      <button v-on:click="++article.pageViews">Increment Page Views</button>
    </div>
  `,
  setup: function() {
    const article = Vue.reactive({ title: 'Vue 3 Reactivity', pageViews: 100 });
    return { article };
  }
});

Vue School has some of our favorite Vue video courses. Their Vue.js Master Class walks you through building a real world application, and does a great job of teaching you how to integrate Vue with Firebase. Check it out!


Did you find this tutorial useful? Say thanks by starring our repo on GitHub!

More Vue Tutorials