Active effect pattern enables dependency tracking
An effect is wrapped so that it sets activeEffect = effect before running the update function and sets activeEffect = null after. This enables track() calls during the update to locate and register the current active effect as a subscriber.
Vue components use reactive effects for DOM updates
Each Vue component instance creates a reactive effect to render and update the DOM. This effect automatically re-runs whenever any reactive state it accesses changes, keeping the DOM in sync with state.
Vue 3 Options API implemented on top of Composition API
In Vue 3, the Options API is implemented on top of the Composition API. All property access on the component instance (this) triggers getter/setters for reactivity tracking. Options like watch and computed invoke their Composition API equivalents internally.
Runtime vs compile-time reactivity trade-offs
Vue's reactivity is runtime-based: tracking and triggering occur while code runs in the browser. Runtime reactivity works without a build step and has fewer edge cases, but is constrained by JavaScript syntax limitations, necessitating value containers like refs. Compile-time reactivity (used by Svelte) transforms code during compilation to simulate reactivity, enabling tracking of local variables but requiring a build step.
DebuggerEvent type structure
DebuggerEvent has type: { effect: ReactiveEffect, target: object, type: TrackOpTypes ('get' | 'has' | 'iterate') | TriggerOpTypes ('set' | 'add' | 'delete' | 'clear'), key: any, newValue?: any, oldValue?: any, oldTarget?: Map<any, any> | Set<any> }
Integrating external state systems with shallowRef
To integrate Vue's reactivity with external state management systems, use shallowRef to hold the external state. A shallow ref is only reactive when its .value property is accessed; the inner value is left intact. When external state changes, replace the ref value by assigning to .value to trigger updates.
Immer integration example with Vue
Immer can be integrated with Vue via a composable that uses shallowRef. The pattern: const state = shallowRef(baseState); const update = (updater) => { state.value = produce(state.value, updater) }. This allows using immutable data structures with mutable syntax ergonomics.
XState integration example with Vue
XState can be integrated via a composable using shallowRef. Create a machine, interpret it, subscribe to state transitions to update the ref value, and return both the state ref and a send function for dispatching events.
Signals and refs are equivalent reactivity primitives
Signals (from Solid, Angular, Preact, Qwik) and Vue's refs are the same kind of reactivity primitive: a value container providing dependency tracking on access and side-effect triggering on mutation. This paradigm dates back more than a decade to Knockout observables and Meteor Tracker.
Solid signals API: read/write segregation
Solid's createSignal() exposes signals as a read-only getter and separate setter: const [count, setCount] = createSignal(0). Access value via count(), update via setCount(1). This segregates read and write to prevent unintended mutations unless the setter is explicitly exposed.
Angular signals API: callable with set and update methods
Angular signals are created via signal(0) and work as callables: count() reads the value, count.set(1) sets it, count.update((v) => v + 1) updates based on previous value. This provides a consistent getter interface with separate mutation methods.
Replicating Solid signals pattern in Vue
Solid signals can be replicated in Vue using shallowRef and triggerRef. Export a function that takes initial value and options, creates a shallowRef, returns [getter, setter] tuple where getter returns r.value and setter updates it, optionally triggering manually if equals: false.
Replicating Angular signals pattern in Vue
Angular signals can be replicated in Vue by creating a function that returns a callable shallowRef accessor with .set and .update methods. The callable returns r.value. set(value) assigns r.value. update(updater) assigns r.value = updater(r.value).
API trade-offs: getter-based vs .value-based signals
Getter-based signals (Solid, Angular style) make value access slightly less verbose with () versus .value, but updating is more verbose. No ref-unwrapping means value access is consistent everywhere and raw signals can be passed as component props. Whether to use getter-based or .value-based APIs is subjective and depends on project requirements.