概述
在 Vue 开发中,修改了数据但界面未更新是最令开发者头疼的问题之一。这通常源于对响应式系统边界的误解。本文将从底层源码逻辑与工程实践两个维度,结合 Vue 2 与 Vue 3 的核心差异,提供系统性的解决方案。
一、响应式系统底层原理深度剖析
1. Vue 2 基于拦截的响应式系统
Vue 2 使用 Object.defineProperty 进行数据劫持。其核心流程是一个闭环:初始化劫持 -> 依赖收集 -> 派发更新。
核心流程图
- 依赖收集阶段
- 组件 Render 访问数据
- Observer 遍历对象,Object.defineProperty 劫持
- 定义 Getter/Setter,触发 Getter
- Dep 记录当前 Watcher,建立映射关系
- 派发更新阶段
- 修改数据,触发 Setter
- Dep 通知所有 Watcher
- Watcher 调用 update,加入异步队列 Queue
- nextTick 刷新 DOM
原理深度解析
- Observer: 将 data 中的所有属性递归地转换为 getter/setter。
- Dep: 一个发布者模式的管理器,每个属性都有一个 Dep 实例,用来存储订阅该属性的 Watcher。
- Watcher: 组件的渲染函数或计算属性,被封装成一个 Watcher。
- 关键缺陷:
- 性能瓶颈: 初始化时就需要递归遍历所有数据,大量数据时耗时长。
- 检测盲区: 无法检测对象属性的新增/删除;无法检测通过索引直接修改数组项。
2. Vue 3 基于 Proxy 的响应式系统
Vue 3 使用 ES6 的 Proxy 代理整个对象,配合 Reflect 进行操作。这是一个惰性的、更高效的系统。
核心流程图
- Track (依赖收集): 读取属性 get -> Reflect.get -> Proxy Get Handler -> track target, key -> activeEffect 是否存在?-> 从 targetMap 获取 depsMap -> dep.add activeEffect
- Trigger (派发更新): 设置属性 set -> 新值 === 旧值?-> Reflect.set 赋值 -> trigger target, key -> 查找关联 effects -> scheduler 调度器 -> 加入 job 队列 -> nextTick 循环中刷新 -> 组件重新渲染
原理深度解析
- Proxy: 不需要递归遍历,而是代理对象本身。只有当属性被访问(触发 get)时,如果发现是对象,才会递归地进行代理(惰性代理)。
- WeakMap: 用于存储依赖关系。Key 是原始对象,Value 是一个 Map(Key 是属性名,Value 是 Set of Effects)。这种结构允许内存垃圾回收机制在对象销毁时自动清理依赖。
- 优势: 完美解决了 Vue 2 的检测盲区,性能大幅提升。
二、UI 未更新的常见场景与深度解决方案
场景 1:对象属性动态添加/删除
Vue 2 现象
this. = { : };
.. = ;
..;


