How does reactivity work in Vue.js?
sobes.tech AI
Answer from AI
Reactivity in Vue.js is based on a dependency tracking system for properties.
When creating a Vue instance, data in data is converted into reactive. For primitives (string, number, boolean), Object.defineProperty (in Vue 2) or Proxy (in Vue 3) is used to intercept get and set operations. For objects and arrays, property traversal and array method replacement are also applied to notify about changes.
When a property is read in a template or computed property, a dependency is created. Vue's internal mechanism (Watcher) remembers which watcher depends on this property.
When a reactive property changes, its setter notifies all dependent watchers. Watchers, in turn, trigger recalculation of computed properties or component re-rendering.
Vue 3 uses the Proxy API, which provides more complete reactivity, including adding, removing, and changing array elements by index.
Key components:
- Observer/Reactive Proxy: Converts data into reactive, tracking get and set.
- Dep (Dependency): A class that collects watchers depending on a specific property.
- Watcher: An entity that monitors property changes and triggers actions (recalculation or re-rendering).
Example of Vue 2 with Object.defineProperty:
// Vue's internal mechanism
function defineReactive(obj, key, val) {
// Create a Dep object for this property
const dep = new Dep();
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter() {
// If there is an active Watcher, add it to Dep
if (Dep.target) {
dep.depend(); // Add dependency Watcher -> Dep
}
return val;
},
set: function reactiveSetter(newVal) {
if (newVal === val) {
return;
}
val = newVal;
dep.notify(); // Notify all dependent Watchers
}
});
}
Example of Vue 3 with Proxy:
// Vue's internal mechanism using Proxy
function createReactiveObject(target) {
return new Proxy(target, reactiveHandlers); // reactiveHandlers contain get/set logic
}
const reactiveHandlers = {
get(target, key, receiver) {
track(target, 'get', key); // Track property read
return Reflect.get(target, key, receiver);
},
set(target, key, value, receiver) {
const result = Reflect.set(target, key, value, receiver);
trigger(target, 'set', key, value); // Trigger update
return result;
}
};