Vue 3 reactivity: Proxy vs Vue 2 getter/setter
- Last updated
- Prerequisites:
- Vue component basics
- JavaScript Proxy and Object.defineProperty
- vue
- reactivity
- proxy
- defineproperty
- vue3
- javascript
Read at your depth
The practical view
Vue 2 wrapped object properties with Object.defineProperty, converting each existing key into a reactive getter/setter. That is why adding a new property to a reactive object required this.$set(obj, 'key', value) — the new key had no getter/setter, so it was invisible to reactivity. Vue 3 uses JavaScript Proxy: reactive(obj) returns a proxy that intercepts get, set, has, deleteProperty, and more, so brand-new keys are reactive automatically, no $set needed. In practice this means arrays indexed by number, added properties, and destructuring behavior all changed: obj['newKey'] = x just works, and arr[3] = v is tracked.
The same idea in other frameworks
const [obj, setObj] = useState({ count: 0 });
// updates are immutable: setObj({ ...obj, count: obj.count + 1 })const count = signal(0);
// signals track reads via a consumer graphconst state = reactive({ count: 0 });
// state.count++ is intercepted by the Proxy set traplet obj = $state({ count: 0 });
// deep reactive via compiled proxies; obj.count++ worksLegacy vs modern
Adding a key to a reactive object: $set vs direct assignment
Vue 2 required $set for new keys because defineProperty ran once at init; the Proxy in Vue 3 makes direct assignment reactive for any key, existing or new.
this.$set(this.user, 'role', 'admin');
// plain assignment would not trigger updates:state.user.role = 'admin'; // tracked by the set trapInterview gotchas
Context
Interviewers ask this to check whether you understand defineProperty's initialization-time limitation versus Proxy's universal interception, not just that $set 'was annoying'.
The mechanical answer
Object.defineProperty must be called for each key at object-creation time to install the getter/setter. Vue 2 walked data() once; keys added later had no traps, so reads/writes bypassed tracking entirely. The ecosystem patched this with $set (which re-walks and redefines the key) and $delete. Vue 3's reactive() returns a Proxy whose set/get traps fire for every property access, so adding, deleting, and iterating are all intercepted natively. That also fixed the class of bugs where replace-with-array-index or length mutation were untracked in Vue 2.
Trap
A common trap is saying 'Vue 3 removed $set because proxies are faster'. Proxies are generally not faster than plain defineProperty access — the real win is correctness and universality (new keys, Maps/Sets, in-operator). Also, candidates sometimes claim reactive() mutates the original object; it does not — the original stays a raw object, and the proxy wraps it, which is why passing raw objects out of reactive can silently break tracking.
Context
This probes practical reactivity literacy — the interviewer wants the mechanical reason behind the .value rule.
The mechanical answer
reactive() wraps objects/arrays in a Proxy and exposes their properties directly. ref() wraps any value (including primitives, which Proxy cannot wrap) in an object with a value property whose getter/setter run track/trigger. When you destructure a reactive object, the proxy is bypassed — const { count } = state copies the raw number and loses tracking, which is why toRefs exists. For refs, the .value indirection keeps the tracked box alive through destructuring. The deep-reactivity default means reactive(ref(x)) unwraps the ref (a nested ref is unwrapped on access via the ref proxy's get trap), which candidates often forget.
Trap
The classic trap: 'reactive only works with objects and ref only with primitives' is almost right but misleading — ref works with objects too (wrapping them in .value), and reactive objects containing refs auto-unwrap them. The deeper trap is destructuring: const { count } = reactive(...) looks like it should work and silently doesn't, which is a favorite interview setup for checking whether the candidate understands that the proxy is the tracked object, not its contents.