[Vue 3] Why both Ref and Reactive are needed
Declaring Reactive Data while working with the Options API was straightforward. Everything inside the data option was automatically made…
![[Vue 3] Why both Ref and Reactive are needed](/images/-Vue-3--Why-both-Ref-and-Reactive-are-needed-344bb5da2593/img-1.png)
[Vue 3] Why both Ref and Reactive are needed
Declaring Reactive Data while working with the Options API was straightforward. Everything inside the data option was automatically made reactive and was available in the template. The only caveat was to make data a function and prevent sharing state across all component instances.
With Composition API things are not that simple. State declaration has to be done using the two available utility functions (ref and reactive), with each one behaving differently.
Let’s discuss what changed in Vue 3 and why we need two different helpers.
Reactivity in Vue 2
Every property inside the data component option will be converted to getter/setters using Object.defineProperty. The getter/setters are invisible to us, but under the hood, they enable Vue to perform dependency-tracking when properties are accessed or modified.
Every component has an associated watcher that tracks the properties used during the component’s render cycle. If a dependency is updated, the watcher notifies the component, which then triggers a re-render.
Reactivity in Vue 3
In Vue 3 everything changed. The core was rewritten from scratch and reactivity is now powered by Javascript [Proxies](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy). Proxies are a modern and elegant way to observe an object and get notified when a property is accessed or updated.
You can understand how proxies work with the following simple example:
const userInfo = {
firstName: "fotis",
age: 35,
};
const handler = {
get(target, property) {
if (property === "firstName") {
const name = target[property];
return name.charAt(0).toUpperCase() + name.slice(1);
}
if (property === "age") {
return "--";
}
return target[property];
},
};
const proxy = new Proxy(userInfo, handler);
console.log(proxy.firstName); // "Fotis"
console.log(proxy.age); // "--"
The get method inside the handler is called a trap and will be called every time a property of the object is accessed.
In a similar manner, a set trap can be defined:
const userInfo = {
firstName: "Fotis",
age: 35,
};
const handler = {
set(target, prop, value) {
if (prop === "age") {
if (!Number.isInteger(value)) {
throw new TypeError("The age is not an integer");
}
if (value > 200) {
throw new RangeError("The age seems invalid");
}
}
target[prop] = value;
return true;
},
};
const proxy = new Proxy(userInfo, handler);
proxy.age = 12; // OK
proxy.age = 300; // Error: The age seems invalid
This is exactly the idea behind Vue 3 reactivity. When a variable is declared using the reactive helper, a proxy is used to keep track of any changes.
function reactive(obj) {
return new Proxy(obj, {
get(target, key) {
track(target, key);
return target[key];
},
set(target, key, value) {
target[key] = value;
trigger(target, key);
},
});
}
Of course, the actual implementation of the reactive helper is more sophisticated and handles edge cases but at the core it still uses
[a proxy](https://github.com/vuejs/core/blob/main/packages/reactivity/src/reactive.ts#L279-L282).
The snippet above explains why destructuring or reassigning a reactive variable to a local variable is no longer reactive because it no longer triggers the get/set proxy traps on the source object.
This looks like a perfect solution to make everything reactive. But there is a catch! By definition, proxies only work for complex types. These are Objects, Arrays, Maps and Sets. To make a primitive reactive we still use a Proxy but we have to wrap it in an Object first.
function ref(value) {
const refObject = {
get value() {
track(refObject, "value");
return value;
},
set value(newValue) {
value = newValue;
trigger(refObject, "value");
},
};
return refObject;
}
This explains the annoying .value that has to be used inside script setup. And again restructuring or reassignment to a local variable doesn’t work.
Conclusion
So the answer to why both Ref and Reactive are needed is: Proxies. For complex types, they can be used directly but on primitives, a proxy object needs to be created.
Hopefully, understanding how Vue works under the hood can make you more effective and can clear any confusion between ref and reactive.


