Vue3响应式原理解析与手写Proxy响应式系统:从track到trigger
Vue3响应式原理解析与手写Proxy响应式系统从track到trigger在现代前端框架中Vue 3 的响应式系统Reactivity System以其精妙的设计、零侵入的 API 以及极致的性能成为了业界学习数据驱动视图的最佳范式之一。与 Vue 2 基于Object.defineProperty的劫持方案相比Vue 2 无法原生监听对象属性的动态添加与删除必须借助Vue.set且对原生数组变异方法的拦截需要 hack 重写其原型链Vue 3 全面基于 ES6 的Proxy与ReflectAPI不仅完美支持对象新增属性、删除属性和数组原生操作的深度拦截更在依赖收集Dependency Tracking的性能上获得了质的飞跃。为了彻底吃透 Vue 3 响应式的底层脉络本文从零手写实现一个包含reactive、effect、track依赖收集与trigger派发更新的极简微型响应式内核。响应式核心架构WeakMap 依赖图谱模型target (原始对象如 { count: 0, text: hello }) │ ▼ (存储在全局 targetMap: WeakMapTarget, MapKey, SetEffect) ┌─────────────────────────────────────────────────────────────┐ │ Key: count ──► Set [ effectFn1, effectFn2 ] (订阅者集合) │ │ Key: text ──► Set [ effectFn3 ] │ └─────────────────────────────────────────────────────────────┘ ▲ (触发 get 读取属性时执行 track 依赖收集) ▼ (触发 set 修改属性时执行 trigger 遍历执行 Set 中的 effect)第一步手写track依赖收集与trigger派发更新全局维护一个targetMap用来存储对象、属性与副作用函数之间的映射关系// mini-reactivity/core.ts // 当前正在激活执行的副作用函数用于解决嵌套 effect 问题需维护调用栈 let activeEffect: (() void) | undefined undefined; const effectStack: Array() void []; // 全局依赖收集大字典: WeakMap - Map - Set const targetMap new WeakMapobject, Mapstring | symbol, Set() void(); // 1. 依赖收集函数在读取属性get时自动被调用 export function track(target: object, key: string | symbol) { if (!activeEffect) return; // 当前没有正在运行的 effect直接忽略 let depsMap targetMap.get(target); if (!depsMap) { targetMap.set(target, (depsMap new Map())); } let dep depsMap.get(key); if (!dep) { depsMap.set(key, (dep new Set())); } // 将当前副作用函数加入该 key 的订阅者集合 dep.add(activeEffect); } // 2. 派发更新函数在修改属性set时自动被调用 export function trigger(target: object, key: string | symbol) { const depsMap targetMap.get(target); if (!depsMap) return; const dep depsMap.get(key); if (dep) { // 拷贝一份 Set 运行防止在执行 effect 期间死循环添加自身 const effectsToRun new Set(dep); effectsToRun.forEach((effectFn) effectFn()); } }第二步手写reactive响应式对象代理工厂利用Proxy和Reflect拦截对象的读写行为// mini-reactivity/reactive.ts import { track, trigger } from ./core; // 防止重复代理同一对象 const proxyMap new WeakMapobject, any(); export function reactiveT extends object(target: T): T { if (typeof target ! object || target null) { return target; } // 若该对象已经被代理过直接返回已有 Proxy if (proxyMap.has(target)) { return proxyMap.get(target); } const proxy new Proxy(target, { // 拦截属性读取 get(target, key, receiver) { // 1. 执行依赖收集 track(target, key); const res Reflect.get(target, key, receiver); // 深度响应式转换 (递归代理嵌套对象惰性转换性能极高) if (typeof res object res ! null) { return reactive(res); } return res; }, // 拦截属性设置 set(target, key, value, receiver) { const oldValue (target as any)[key]; const result Reflect.set(target, key, value, receiver); // 仅当值真正改变时才触发更新判定 NaN 异常 if (hasChanged(value, oldValue)) { // 2. 派发更新通知所有订阅者 trigger(target, key); } return result; } }); proxyMap.set(target, proxy); return proxy; } function hasChanged(value: any, oldValue: any): boolean { return !Object.is(value, oldValue); }第三步手写effect副作用调度器// mini-reactivity/effect.ts export function effect(fn: () void) { const effectFn () { try { activeEffect effectFn; effectStack.push(effectFn); // 执行用户传入的业务函数执行过程中若访问了响应式属性会自动触发其 get - track() return fn(); } finally { effectStack.pop(); activeEffect effectStack[effectStack.length - 1]; } }; // 默认立即执行一次以完成首次依赖收集 effectFn(); }运行验证见证数据驱动的魔力// test/demo.ts const state reactive({ title: AI 周报生成器, viewCount: 100 }); // 注册副作用监听 effect(() { console.log([UI 视图更新] 标题: ${state.title} | 浏览量: ${state.viewCount}); }); // 控制台立即输出: [UI 视图更新] 标题: AI 周报生成器 | 浏览量: 100 // 修改属性自动触发更新 state.viewCount 1; // 控制台自动输出: [UI 视图更新] 标题: AI 周报生成器 | 浏览量: 101 state.title 周报生成器 2.0; // 控制台自动输出: [UI 视图更新] 标题: 周报生成器 2.0 | 浏览量: 101架构哲学与感悟Vue 3 响应式系统的伟大之处在于将复杂的“数据观察者模式”精简为了极其纯粹的WeakMap存储结构 Proxy拦截管道。理解了track与trigger的流动本质你在面对 Vue 3 的computed、watch以及深层响应式调试时便能拨开语法糖的迷雾直接在底层运行时视角洞察一切数据流转。