Introduction to Vue 3 API and Reactive System

Vue 3 replaces Vue 2’s Object.defineProperty reactivity with a Proxy‑based system, using the Composition API (e.g., a reusable usePosition hook) to create reactive state, cache proxies via WeakMaps, differentiate array additions from length changes, and track dependencies through effect stacks for efficient updates.

HelloTech
HelloTech
HelloTech
Introduction to Vue 3 API and Reactive System

Vue 3 introduces a new set of APIs that replace the options‑based syntax of Vue 2.x while keeping the same functionality. The article demonstrates a simple example ( usePosition) that shows how to extract reusable logic with the Composition API.

function usePosition(){
  let position = Vue.reactive({x:0,y:0});
  function update(e){
      position.x = e.pageX;
      position.y = e.pageY;
  }
  Vue.onMounted(() =>{ // lifecycle hook
      window.addEventListener('mousemove',update)
  })
  Vue.onUnmounted(() =>{
      window.removeEventListener('mousemove',update)
  });
  return Vue.toRefs(position);
}
const App = {
  setup(props, context){ // similar to created, runs once
    let state = Vue.reactive({name:'syh'}); // reactive state
    let position = usePosition();
    function change(){    // methods
      state.name = 'syh1'
    }
    return { // render context
      state,
      change,
      ...position // spread reactive values, must use toRefs to keep reactivity
    }
  },
  template:`<div @click="change">{{state.name}} x:{{x}} y{{y}}</div>`
}
Vue.createApp(App).mount(container) // mount the app

The article then reviews Vue 2.x's reactivity implementation, showing how Object.defineProperty is used to intercept property access and mutation. It points out three main drawbacks: excessive recursive traversal, special handling for arrays, and interception of non‑existent properties.

// Simplified Vue 2 reactive core
let oldArrayPrototype = Array.prototype;
let proto = Object.create(oldArrayPrototype);
['push','shift','unshift','pop'].forEach(method => {
  proto[method] = function(){
    updateView(); // AOP update view
    oldArrayPrototype[method].call(this,...arguments);
  }
});
function observer(target){
  if(typeof target !== 'object' || target == null) return target;
  if(Array.isArray(target)){
    Object.setPrototypeOf(target,proto);
    for(let i=0;i<target.length;i++) observer(target[i]);
  }else{
    for(let key in target) defineReactive(target,key,target[key]);
  }
}
function defineReactive(target,key,value){
  observer(value);
  Object.defineProperty(target,key,{
    get(){ return value; },
    set(newValue){
      if(newValue !== value){
        observer(newValue);
        updateView();
        value = newValue;
      }
    }
  });
}
function updateView(){ console.log('更新视图') }
let data = {name:'syh',age:[1,2,3]};
observer(data);
data.age.push(4);

Vue 3 replaces this with a Proxy‑based implementation. The reactive function creates a Proxy that intercepts get, set and deleteProperty operations, using Reflect to perform the actual operation while logging the trap calls.

function isObject(val) {
  return typeof val === 'object' && val !== null;
}
function reactive(target) {
  return createReactiveObject(target);
}
function createReactiveObject(target) {
  if(!isObject(target)) return target;
  let observed = new Proxy(target, {
    get(target, key, receiver){
      console.info('get');
      let result = Reflect.get(target, key, receiver);
      return isObject(result) ? reactive(result) : result; // recursive
    },
    set(target, key, value, receiver){
      console.info('set');
      let res = Reflect.set(target, key, value, receiver);
      return res;
    },
    deleteProperty(target, key){
      console.log('delete');
      let res = Reflect.deleteProperty(target, key);
      return res;
    }
  });
  return observed;
}
let proxy = reactive({ name: 'syh' });
proxy.name;          // triggers get
proxy.name = 'syn1'; // triggers set
delete proxy.name;   // triggers delete

To avoid creating multiple proxies for the same object, Vue 3 uses two WeakMap tables ( toProxy and toRaw) that cache the relationship between the raw object and its proxy. Before creating a new proxy, the implementation checks these maps and returns the existing proxy if it already exists.

let toProxy = new WeakMap(); // raw -> proxy
let toRaw = new WeakMap();   // proxy -> raw
function reactive(target){
  if(toProxy.has(target)) return toProxy.get(target);
  if(toRaw.has(target)) return target; // already a proxy
  const observed = new Proxy(target, handlers);
  toProxy.set(target, observed);
  toRaw.set(observed, target);
  return observed;
}

Array handling is refined by distinguishing between adding a new element and modifying the length property. The hasOwn helper checks whether a key already exists on the target, allowing the set trap to emit different effect types ( add vs set).

function hasOwn(target, key){
  return Object.prototype.hasOwnProperty.call(target, key);
}
function set(target, key, value, receiver){
  const hasKey = hasOwn(target, key);
  const oldValue = target[key];
  const res = Reflect.set(target, key, value, receiver);
  if(!hasKey){
    console.info('新增');
    trigger(target, 'add', key);
  } else if(oldValue !== value){
    console.info('修改属性');
    trigger(target, 'set', key);
  }
  return res;
}

Dependency collection in Vue 3 is built around an effect function. When an effect runs, it is pushed onto a stack ( activeEffectStack). The get trap calls track to associate the accessed property with the current effect. The track function stores the relationship in a global WeakMap ( targetsMap) where each target maps to a Map of keys to a Set of effects.

let targetsMap = new WeakMap();
function track(target, key){
  const effect = activeEffectStack[activeEffectStack.length-1];
  if(!effect) return;
  let depsMap = targetsMap.get(target);
  if(!depsMap){
    depsMap = new Map();
    targetsMap.set(target, depsMap);
  }
  let deps = depsMap.get(key);
  if(!deps){
    deps = new Set();
    depsMap.set(key, deps);
  }
  if(!deps.has(effect)) deps.add(effect);
}
function trigger(target, type, key){
  const depsMap = targetsMap.get(target);
  if(!depsMap) return;
  const deps = depsMap.get(key);
  if(deps){
    deps.forEach(effect => effect());
  }
}
function effect(fn){
  const reactiveEffect = () => {
    try{
      activeEffectStack.push(reactiveEffect);
      fn();
    }finally{
      activeEffectStack.pop();
    }
  };
  reactiveEffect();
}

With these mechanisms, Vue 3 achieves lazy recursive proxying, efficient duplicate‑proxy avoidance, fine‑grained dependency tracking, and correct handling of arrays and nested objects.

References: Zhihu article on Vue 3 API, GitHub repositories, Juejin posts on Vue 3 source code, and additional blog posts on WeakMap usage.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaScriptProxyVue.jsAPIreactive
HelloTech
Written by

HelloTech

Official Hello technology account, sharing tech insights and developments.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.