Vue 3 Reactivity: Proxies vs. Vue 2 Reactivity

Abstract image used to represent Vue 3 Reactivity: Proxies vs. Vue 2 Reactivity
Image by Bruce Warrington.

One of the biggest improvements in Vue 3 is its completely redesigned reactivity system. Whilst effective, Vue 2's reactivity system had some notable limitations, especially when working with arrays or dynamically adding properties. Vue 3 leverages JavaScript's builtin Proxy feature to solve many of these issues.

In this article, I intend to explore the differences between Vue 2's reactivity approach and Vue 3's use of Proxies and explain how each system works and why Vue made the switch.


How Reactivity Worked in Vue 2

Before looking at Vue 3's improvements, it makes sense for us to briefly revisit how Vue 2's reactivity works.

Vue 2: Object.defineProperty()

In Vue 2, the reactivity system relied heavily on JavaScript's Object.defineProperty() method (which I've written about in the past here). This method defines getters and setters on object properties, allowing Vue to detect and respond to changes, like this:

const data = {};
Object.defineProperty(data, 'message', {
  get() {
    console.log('Accessing message');
    return 'Hello';
  },
  set(newValue) {
    console.log('Updating message to:', newValue);
  },
});

This small example only logs access and assignment; it is not a complete reactivity system. Vue 2 uses getters to track which properties a render depends on, and setters to notify those dependants when a value changes. Reading message does not itself mean the DOM needs updating.

Limitations of Vue 2 Reactivity

This approach has a few limitations:

  • New properties added after initialisation are not reactive.
  • Array modifications require special methods (push, splice, etc.) to trigger updates.
  • It cannot detect changes to properties that weren't initially defined.

The awkward part was remembering which updates needed a helper. An ordinarylooking assignment could change the data without notifying the view.


How Vue 3 Uses Proxies for Reactivity

Vue 3 uses JavaScript Proxy objects to intercept operations on a reactive object, including additions and deletions that Vue 2's initial getterandsetter conversion could not observe directly.

What are JavaScript Proxies?

A Proxy is an object which wraps another object (the 'target') and then intercepts operations such as reading, writing, or deleting properties. It can then handle these operations or pass them through to the underlying object.

Here's a quick example:

const target = { message: 'Hello' };

const proxy = new Proxy(target, {
  get(target, prop) {
    console.log(`Property "${String(prop)}" read.`);
    return target[prop];
  },
  set(target, prop, value) {
    console.log(`Property "${String(prop)}" updated to "${value}"`);
    target[prop] = value;
    return true;
  },
});

console.log(proxy.message);  // logs "Hello"
proxy.message = 'Hi!';  // triggers setter

That gives Vue 3 a way to observe more kinds of operation. It does not mean that using a native Proxy makes every workload faster; performance also depends on Vue's implementation and the application being measured.


How Vue 3 Uses Proxies for Enhanced Reactivity

By adopting Proxies, Vue 3's reactivity system provides several advantages:

Dynamic Property Reactivity

Properties added after initialisation become reactive automatically, for example:

const state = Vue.reactive({});

// Adding a new reactive property dynamically
state.newMessage = 'Hello Vue!';

This means that Vue 3 immediately tracks and responds to a new property, something that Vue 2 could not easily do.

Better Array Reactivity

Arrays wrapped in Vue 3's reactive() can track direct index assignment and changes to length. Those are useful differences from Vue 2; push() already triggered reactive updates there:

import { reactive } from 'vue';

const state = reactive({ items: ['a', 'b', 'c'] });

state.items[1] = 'updated';  // Direct index assignment is reactive
state.items.length = 1;  // Changing the length is reactive too

In Vue 2, an index replacement needed Vue.set() or splice(), and shortening an array needed splice(). Vue.set() is a framework helper, not an array method. Vue 3 lets us use the direct assignments shown above on the reactive array.


Comparing Vue 2 and Vue 3 Reactivity

To summarise, here's a brief overview of the main differences between the two approaches:

FeatureVue 2 ReactivityVue 3 Reactivity
ImplementationGetters/SettersJavaScript Proxies
New PropertiesDeclare initially or use Vue.set() on a nested objectAdd properties through the reactive proxy
Array ReactivityIndex and length assignment need reactive helpersIndex and length assignment are tracked on reactive arrays
PerformanceMeasure the application and workloadMeasure the application and workload; Proxy alone is not a speed guarantee
Browser SupportWider browser compatibilityRequires modern browsers

Browser Support and Polyfills

Browser support needs a wider check than whether Proxy exists. Vue 3 requires modern JavaScript support and does not support Internet Explorer 11. Check the Vue browser requirements, the particular Vue release and the build's output target; the first browser versions to implement Proxy are not a complete Vue 3 support matrix.

The Proxy behaviour used by Vue 3 cannot be supplied faithfully by a polyfill. A legacybrowser requirement therefore needs an explicit product decision rather than a routine fallback to Vue 2, which reached end of life on 31 December 2023. Existing Vue 2 applications should plan a migration or obtain maintained extended support; new work should use Vue 3.


Wrapping Up

Key Takeaways

  • Vue 2 used getters and setters (Object.defineProperty) to track changes, leading to certain limitations.
  • Vue 3 uses JavaScript proxies, offering improved flexibility and automatic reactivity.
  • Property additions, direct array index assignment and array length changes are tracked when made through Vue 3's reactive proxies.
  • Proxy lets Vue observe more operations; measure performance on the application rather than assuming a universal speed improvement.
  • Browser support for proxies is strong, but Vue 3's Proxybased reactivity cannot be polyfilled for unsupported legacy browsers.

Vue 3 can observe property additions, deletions, arrays, and collection types more naturally because a Proxy intercepts operations on the object itself. Vue 2 had to convert known properties with getters and setters. The improvement is substantial, but destructuring reactive values and relying on object identity can still break expectations.


Have a complex web platform issue?

Tell me what is blocked, what has changed, and what needs to be true after the fix. I'll come back with a practical next step.