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.
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 appThe 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 deleteTo 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.
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.
HelloTech
Official Hello technology account, sharing tech insights and developments.
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.
