Unlocking Vue 3 Reactivity: Deep Dive into Reactive, Ref, Computed, and Watch
This article explains how Vue 3’s reactivity system works—covering the creation of reactive proxies, the role of Ref, the mechanics of computed properties, and the behavior of watch and watchEffect APIs—while illustrating common pitfalls and optimization techniques with code examples and diagrams.
Liu Chongzhen, a frontend engineer at WeDoctor Cloud Services team, a non‑typical coder who holds a baby in one hand and a house in the other.
Background: during Vue3 initialization we left a TODO pitfall: Reactive in setupStatefulComponent, introducing where reactivity is initialized.
We set a breakpoint on the reactive function call, review the call stack of the Vue3 initialization process, and connect it to this Reactive introduction.
In our demo we look at the setup function and use a small code snippet to explore Vue 3.0’s reactivity.
1. Reactive
The implementation of reactive uses createReactiveObject to convert a target into a reactive object.
Consider the target object target and its flag property for easier understanding.
export interface Target {
[ReactiveFlags.SKIP]?: boolean // skip, do not make target reactive
[ReactiveFlags.IS_REACTIVE]?: boolean // target is reactive
[ReactiveFlags.IS_READONLY]?: boolean // target is readonly
[ReactiveFlags.RAW]?: any // raw source of target, not proxied
}1.1 createReactiveObject
createReactiveObjectconverts target into a reactive object by creating a new Proxy. Unlike the old defineProperty API, Proxy can proxy arrays and, together with Reflect, perfectly implements trap interception.
Through the proxy ’s target, we can perform trap operations such as get, set, deleteProperty, has, and ownKeys. The handlers differ for Object and Array, using baseHandlers for both.
let proxy = new Proxy(target, handlers);
// set trap
let proxy = new Proxy(target, {
get(target, key, receiver) {
return Reflect.get(target, key, receiver)
},
set(target, key, value, receiver) {
return Reflect.set(target, key, value, receiver)
}
})All uses of the Proxy follow this pattern; only the handler definition varies.
Note: the argument to reactive must be an object . To make primitive values reactive, use ref , which accepts both primitive and reference types.
For arrays, Vue 3 hacks methods like indexOf, push, etc., storing them in arrayInstrumentations. The hack tracks each element during iteration and pauses tracking for length‑changing methods to avoid redundant trigger calls.
// length‑changing method always triggers .length property
const arr = [1, 2];
const proxy = new Proxy(arr, {
get(target, key, receiver) {
console.log('get', key);
return Reflect.get(target, key, receiver);
},
set(target, key, value, receiver) {
console.log('set', key, value);
return Reflect.set(target, key, value, receiver);
},
});
proxy.unshift(3);
// logs: 'get' 'unshift', 'get' 'length', 'get' '1', 'set' '2' 2, ...Vue uses pauseTracking() to set shouldTrack to false during such operations, and resetTracking() restores the previous state, preventing unnecessary dependency collection.
2. Effect (Side Effects)
An effect in Vue 3.0 is the dependency or side‑effect that links a reactive data source with a callback function.
setupRenderEffectmounts an update function (an immediate effect) on the component instance. When the effect runs, it cleans up previous dependencies, pauses tracking during setup, pushes the effect onto a global effectStack, sets it as the active effect, runs the render function (collecting dependencies via getters), and finally pops the stack.
The overall reactive workflow: setupRenderEffect creates an immediate update effect.
The effect cleans up and re‑collects dependencies each run.
During execution, the effect’s getter triggers collect via track.
When reactive data changes, trigger dispatches updates and batches effect execution.
The loop repeats: 2 → 3 → 4 → 2.
3. Computed Properties
A computed property depends on other state and returns an immutable (or writable) reactive ref object.
computedreceives a getter (e.g., () => state.counter * 2) and returns a read‑only ref. Internally it creates a ComputedRefImpl whose getter lazily runs an effect, tracks dependencies, and caches the value until marked dirty.
get value() {
if (this._dirty) {
this._value = this.effect();
this._dirty = false;
}
track(toRaw(this), TrackOpTypes.GET, 'value');
return this._value;
}The constructor sets up the effect with lazy: true and a scheduler that marks _dirty = true and triggers the dependent effects when the underlying reactive source changes.
this.effect = effect(getter, {
lazy: true,
scheduler: () => {
if (!this._dirty) {
this._dirty = true;
trigger(toRaw(this), TriggerOpTypes.SET, 'value');
}
}
});4. Watch API
The watch API is equivalent to Vue 2’s this.$watch ; it observes a data source and runs a callback when the source changes. By default it is lazy and only runs on change.
Vue 3 also adds watchEffect, which runs the provided function immediately, collects dependencies, and re‑runs when any of those dependencies change.
watchEffect(() => console.log(state.counter));
const count = ref(0);
watch(count, (newVal, oldVal) => { /* ... */ });Both APIs delegate to doWatch(source, cb, options). watchEffect wraps the source function with an onInvalidate cleanup, while watch can accept a source and a callback, optionally providing previous values.
export function watchEffect(effect, options) {
return doWatch(effect, null, options);
}
export function watch(source, cb, options) {
return doWatch(source, cb, options);
}The internal effect created by doWatch uses a scheduler that queues the effect via queuePreFlushCb to avoid multiple executions within the same tick.
Appendix
Vue3 reactivity diagram: Vue3响应式.drawio
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
WeDoctor Frontend Technology
Official WeDoctor Group frontend public account, sharing original tech articles, events, job postings, and occasional daily updates from our tech team.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
