尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

Vue 3 核心响应式与渲染原理全景拆解

Vue 3 核心响应式与渲染原理全景拆解 文章目录Vue 3 核心响应式与渲染原理全景拆解1. 响应式基石Proxy 与 Reflect为什么 Proxy 优于 Object.defineProperty关键代码逻辑简化版2. 依赖收集核心Effect 与全景 targetMap 映射表依赖关系映射结构targetMap3. ref 与 reactive 原理拆解reactiveref4. 渲染与 Diff 优化编译时与运行时的高效协同① Block Tree 与动态节点标记PatchFlag② 最长递增子序列算法Fast Diff5. 组件级渲染机制与异步更新队列异步队列queueJob与 nextTick6. Vue 2 vs Vue 3 全景架构对比总结Vue 3 核心响应式与渲染原理全景拆解如果说 Vue 2 的响应式是一套设计精巧的“面向对象类系统Watcher / Dep”那么 Vue 3 则彻底演变为一套基于 ES6 现代特性的“高并发、轻量化、函数式系统Proxy / Effect”。1. 响应式基石Proxy与ReflectVue 3 抛弃了Object.defineProperty转而使用 ES6 的Proxy代理配合Reflect反射实现数据的响应式劫持。为什么Proxy优于Object.defineProperty维度Vue 2 (Object.defineProperty)Vue 3 (Proxy)劫持对象属性级别需要递归遍历每一个 key对象级别直接代理整个对象新增/删除属性无法侦测需借助this.$set/$delete天然支持set/deleteProperty拦截数组支持依赖拦截重写 7 个变异方法无法感知下标修改天然支持正常拦截数组索引修改与length变化初始化性能递归初始化所有属性层级越深越慢按需惰性代理只有读取到深层对象时才递归创建reactive关键代码逻辑简化版functionreactive(target){if(typeoftarget!object||targetnull)returntarget;returnnewProxy(target,{get(target,key,receiver){// 1. 依赖收集 (track)track(target,key);constresReflect.get(target,key,receiver);// 2. 惰性响应式取值时发现是对象才进行深层代理returntypeofresobjectres!null?reactive(res):res;},set(target,key,value,receiver){constoldValuetarget[key];constresultReflect.set(target,key,value,receiver);// 3. 值发生变化时派发更新 (trigger)if(hasChanged(value,oldValue)){trigger(target,key);}returnresult;}});}2. 依赖收集核心Effect与全景targetMap映射表Vue 3 废弃了 Vue 2 的Dep类引入了全局统一的双重 WeakMap / Map / Set 依赖数据结构targetMap和底层执行单元Effect作用/副作用。依赖关系映射结构targetMap在内存中响应式依赖通过如下关系精确绑定targetMap (WeakMap) └── target 对象 (Key) └── depsMap (Map) └── key 属性 (Key) └── dep (Set: 存放所有依赖该属性的 ReactiveEffect)// 全局依赖树consttargetMapnewWeakMap();letactiveEffectnull;// 当前正在运行的 Effect// 1. 依赖收集trackfunctiontrack(target,key){if(!activeEffect)return;letdepsMaptargetMap.get(target);if(!depsMap){targetMap.set(target,(depsMapnewMap()));}letdepdepsMap.get(key);if(!dep){depsMap.set(key,(depnewSet()));}// 将当前副作用添加进 Set 集合自动去重dep.add(activeEffect);}// 2. 派发更新triggerfunctiontrigger(target,key){constdepsMaptargetMap.get(target);if(!depsMap)return;constdepdepsMap.get(key);if(dep){// 拷贝一份防止无限循环依次执行副作用consteffectsToRunnewSet(dep);effectsToRun.forEach(effect{if(effect.scheduler){effect.scheduler();// 如果有调度器如组件渲染队列、computed 脏标记优先走调度器}else{effect.run();// 否则直接运行}});}}3.ref与reactive原理拆解Vue 3 提供了两种主要的响应式声明 APIreactive和ref。reactive作用对象仅接收复杂数据类型Object / Array / Map / Set。实现原理底层基于 ES6Proxy对对象进行全方位包装。ref作用对象既可以接收原始值String / Number / Boolean也可以接收对象。实现原理由于 ES6Proxy无法直接代理原始值Vue 3 包装了一个RefImpl类利用getter/setter属性访问器拦截.value的读写动作如果ref传入的是对象底层会自动调用reactive转为Proxy。classRefImpl{constructor(value){this._rawValuevalue;// 如果是对象则调用 reactive 代理否则返回原值this._valueisObject(value)?reactive(value):value;this.__v_isReftrue;// 属性标记用于自动解包}getvalue(){trackRefValue(this);// 手动触发 ref 的依赖收集returnthis._value;}setvalue(newVal){if(hasChanged(newVal,this._rawValue)){this._rawValuenewVal;this._valueisObject(newVal)?reactive(newVal):newVal;triggerRefValue(this);// 触发更新}}}4. 渲染与 Diff 优化编译时与运行时的高效协同Vue 3 不仅重构了响应式还大幅优化了编译Compiler与运行时Runtime的配合实现了极其高效的 DOM Diff。① Block Tree 与动态节点标记PatchFlagVue 2 在数据更新时需要逐层对整棵虚拟 DOM 树VNode进行递归对比。Vue 3 引入了静态提升Static Hoisting和动态节点标记PatchFlag动态节点Dynamic Nodes编译阶段分析出哪些节点带数据绑定如:classcls或\{\{ text \}\}并赋予对应的位掩码PatchFlag如1代表仅动态文本2代表仅动态 class。Block 容器根节点将所有动态子节点收集到一个一维数组dynamicChildren中。更新阶段Diff 过程跳过所有静态节点直接遍历dynamicChildren数组并且只对比PatchFlag指定的属性如只比对 class不比对属性与文本。② 最长递增子序列算法Fast Diff在列表 Diff 阶段如v-for顺序大洗牌Vue 2 使用双端对比算法Four-pointer Diff而 Vue 3 采用了效率更高的Fast Diff 算法预处理同步头尾先分别从头、从尾比对相同节点直接复用并挂载。构建映射表对中间剩余的乱序子节点建立新旧索引映射。最长递增子序列LIS求解旧节点在新列表中位置的最长递增子序列。子序列中的节点意味着“相对顺序未变”在 DOM 更新时完全不需要移动从而将 DOM 移动操作降至最低。5. 组件级渲染机制与异步更新队列在 Vue 3 中每个组件实例在挂载mount时都会创建一个专门负责渲染的 ReactiveEffect// 组件渲染逻辑简化constsetupRenderEffect(instance,container){constcomponentUpdateFn(){if(!instance.isMounted){// 1. 首次渲染执行 render 生成 VNode - 挂载 DOMconstsubTree(instance.subTreeinstance.render.call(instance.proxy));patch(null,subTree,container);instance.isMountedtrue;}else{// 2. 重新渲染生成新 VNode - 比对 Diff - 更新 DOMconstnextTreeinstance.render.call(instance.proxy);constprevTreeinstance.subTree;instance.subTreenextTree;patch(prevTree,nextTree,container);}};// 创建渲染 Effect并传入 scheduler 实现批量异步更新consteffect(instance.effectnewReactiveEffect(componentUpdateFn,()queueJob(update)// 数据变化时不立即计算而是入队微任务));constupdate(instance.update()effect.run());update();};异步队列queueJob与nextTick当修改多个ref或reactive属性时trigger会触发effect.scheduler()将更新任务推进全局的queue调度队列。队列通过Promise.resolve().then(flushJobs)在微任务中统一去重并执行按组件创建顺序自顶向下刷新保证组件在同一 Tick 内只会重新 Render 一次。6. Vue 2 vs Vue 3 全景架构对比总结架构模块Vue 2 底层方案Vue 3 底层方案响应式核心Object.defineProperty(Getter/Setter)ES6ProxyReflect依赖管理结构闭包内的Dep实例 Watcher依赖数组全局targetMap(WeakMap) ReactiveEffect(Set)计算属性原理Watcher(lazy: true) dirty标记ComputedRefImplReactiveEffect链式调度侦听器原理User Watcher 递归traverse()依赖收集watch/watchEffectonCleanup副作用清理渲染比对 (Diff)树形全量对比双端 Diff 算法Block Tree PatchFlag靶向比对 最长递增子序列内存开销较重大量的组件级 / 属性级 Watcher 实例化极轻无类实例化开销结构扁平 感谢阅读想了解更多 我的博客网站 | 记录思考分享干货 我的个人主页 | 关于我、开源项目
返回列表