
上篇回顾2.1-03 把 JS 核心特性原型链、闭包、this、事件循环、Promise/async与 Java 对照讲透。本篇进入前端框架——Vue3。对后端工程师来说Vue3 是最友好的框架模板语法接近 HTML响应式让 UI 自动更新。但「数据变了 UI 自动更新」背后到底是什么机制一、开篇从命令式到声明式原生 JS 写一个计数器letcount0;constbtndocument.querySelector(#btn);constdisplaydocument.querySelector(#display);btn.addEventListener(click,(){count;display.textContentcount;// 手动更新 UI});每次数据变手动更新 DOM。这是命令式——你告诉机器每一步怎么做。Vue3 写同样的计数器template button clickcount{{ count }}/button /template script setup import { ref } from vue; const count ref(0); /script数据变了UI 自动更新。这是声明式——你描述状态框架帮你更新 UI。魔法在哪本篇拆解 Vue3 响应式的底层机制。二、Vue2 的 defineProperty为什么不够用Vue2 用Object.defineProperty实现响应式Object.defineProperty(obj,key,{get(){// 收集依赖returnvalue;},set(newVal){valuenewVal;// 触发更新}});2.1 defineProperty 的三大缺陷缺陷说明无法监听新增属性obj.newKey 1不触发响应式要用Vue.set无法监听删除属性delete obj.key不触发响应式数组监听有问题索引修改、length 修改不触发要重写数组方法2.2 Vue2 的 workaround新增属性Vue.set(obj, key, value)或this.$set数组重写 push/pop/splice 等方法深层对象递归遍历所有属性 defineProperty初始化慢这些 workaround 让 Vue2 的响应式代码充满「特殊场景特殊处理」。三、Vue3 的 Proxy全面拦截3.1 Proxy 是什么Proxy 是 ES6 提供的代理机制可以拦截对象的所有操作consthandler{get(target,key,receiver){track(target,key);// 收集依赖returnReflect.get(target,key,receiver);},set(target,key,value,receiver){Reflect.set(target,key,value,receiver);trigger(target,key);// 触发更新returntrue;}};constproxynewProxy(obj,handler);3.2 Proxy 解决了 defineProperty 的所有缺陷问题definePropertyProxy新增属性不触发触发拦截 set删除属性不触发触发拦截 deleteProperty数组索引/length不触发触发深层对象递归初始化惰性代理访问时才代理3.3 惰性代理Vue3 不在初始化时递归代理所有层级而是访问到某层时才代理该层get(target,key,receiver){constresultReflect.get(target,key,receiver);if(isObject(result)){returnreactive(result);// 访问到才代理}returnresult;}对于大对象初始化性能大幅提升。四、依赖收集与触发更新4.1 track读取时收集letactiveEffectnull;functioneffect(fn){activeEffectfn;fn();activeEffectnull;}functiontrack(target,key){if(!activeEffect)return;letdepsMaptargetMap.get(target);if(!depsMap){depsMapnewMap();targetMap.set(target,depsMap);}letdepdepsMap.get(key);if(!dep){depnewSet();depsMap.set(key,dep);}dep.add(activeEffect);}4.2 trigger修改时触发functiontrigger(target,key){constdepsMaptargetMap.get(target);if(!depsMap)return;constdepdepsMap.get(key);if(dep){dep.forEach(effecteffect());}}4.3 完整流程1. 组件渲染时执行 render 函数 → activeEffect render 2. render 读取 count → track(count) → 把 render 加入 count 的依赖集合 3. 用户点击 → count → set 拦截 → trigger(count) → 执行 render → UI 更新五、ref vs reactive5.1 reactive对象响应式import{reactive}fromvue;conststatereactive({count:0,name:Tom});state.count;// 响应式触发reactive 适合对象/数组。基本类型不能用 reactiveProxy 只能代理对象。5.2 ref基本类型响应式import{ref}fromvue;constcountref(0);count.value;// 注意 .valueref 内部用{ value: x }对象包装再用 reactive 代理。模板里自动解包.value。5.3 选择建议场景推荐基本类型ref对象/数组reactive表单整体reactive单个值ref六、组合式 APIComposition API6.1 Options API 的痛点Vue2 的 Options APIexportdefault{data(){return{count:0}},methods:{increment(){this.count}},computed:{double(){returnthis.count*2}},mounted(){console.log(mounted)}}痛点一个功能的代码散落在 data/methods/computed/mounted 四个地方大组件难以维护。6.2 Composition API按功能组织import{ref,computed,onMounted}fromvue;functionuseCounter(initial0){constcountref(initial);constdoublecomputed(()count.value*2);functionincrement(){count.value;}onMounted(()console.log(counter mounted));return{count,double,increment};}// 使用const{count,double,increment}useCounter(10);一个功能的所有逻辑封装在一个函数里可复用、可测试。6.3 setup 语法糖script setup import { ref } from vue; const count ref(0); const increment () count.value; /script template button clickincrement{{ count }}/button /templatescript setup是编译时语法糖省去了export default和return顶层变量自动暴露给模板。七、组件通信方式方向场景props / emit父↔子最常用v-model父↔子表单双向绑定provide / inject祖→孙跨层级传值ref / expose父→子父调子方法事件总线mitt任意全局事件谨慎用Pinia任意全局状态管理推荐7.1 props / emit 示例!-- 父组件 -- template ChildComp :messagemsg replyonReply / /template script setup import { ref } from vue; import ChildComp from ./ChildComp.vue; const msg ref(hello); const onReply (val) console.log(子组件回复:, val); /script !-- 子组件 ChildComp.vue -- template div p{{ message }}/p button clickemit(reply, 收到)回复/button /div /template script setup const props defineProps([message]); const emit defineEmits([reply]); /script八、双向绑定是语法糖还是机制input v-modeltext /v-model是语法糖等价于input :valuetext inputtext $event.target.value /底层机制:valuetext→ 把 text 绑定到 input 的 value 属性input→ 监听 input 事件用户输入时更新 texttext 变了 → 响应式触发 → value 重新绑定所以双向绑定 单向绑定 事件监听的语法糖不是新机制。理解这一点自定义组件的双向绑定就简单了!-- 自定义组件的双向绑定 -- MyInput v-modeltext / !-- 等价于 -- MyInput :modelValuetext update:modelValuetext $event /九、常见陷阱9.1 解构 reactive 丢失响应式conststatereactive({count:0});const{count}state;// count 不再响应式解构后 count 是普通值。要用toRefsconst{count}toRefs(state);// count 是 ref保持响应式9.2 修改 propsconstpropsdefineProps([count]);props.count;// 警告不能直接改 propsprops 是只读的要改就 emit 事件让父组件改。9.3 忘记 .valueconstcountref(0);console.log(count);// 输出 ref 对象不是值console.log(count.value);// 输出 0模板里自动解包JS 里要手动.value。十、小结表概念Vue2Vue3响应式definePropertyProxy新增属性Vue.set自动响应数组监听重写方法自动响应深层代理递归初始化惰性代理API 风格OptionsComposition状态管理VuexPinia