文章目录

Vue 响应式:Vue 2 与 Vue 3 响应式原理

发布于 2026-04-04 09:31:26 · 浏览 5 次 · 评论 0 条

Vue 响应式:Vue 2 与 Vue 3 响应式原理

Vue 的核心特性之一是响应式系统——当你修改数据时,视图会自动更新。这一能力在 Vue 2 和 Vue 3 中实现方式完全不同。理解它们的原理,能帮你写出更高效、更少 bug 的代码。


Vue 2 的响应式:基于 Object.defineProperty

Vue 2 使用 Object.defineProperty 来劫持对象属性的读取(getter)和设置(setter),从而追踪依赖并在数据变化时通知更新。

创建一个响应式对象的步骤如下:

  1. 遍历目标对象的所有属性。
  2. 对每个属性调用 Object.defineProperty,重写其 getset 方法。
  3. get收集依赖(即哪些组件或计算属性用到了这个值)。
  4. set触发更新(通知所有依赖重新计算或渲染)。

例如,当你写:

data() {
  return {
    message: 'Hello'
  }
}

Vue 内部会将其转换为:

Object.defineProperty(data, 'message', {
  get() {
    // 收集当前活跃的 watcher(比如渲染函数)
    dep.depend();
    return value;
  },
  set(newVal) {
    if (newVal === value) return;
    value = newVal;
    // 通知所有依赖更新
    dep.notify();
  }
});

这种方式存在几个硬伤

  • 无法检测新增或删除属性。比如 this.obj.newProp = 'test' 不会触发更新,必须用 Vue.set(obj, 'newProp', 'test')
  • 无法监听数组索引赋值(如 arr[0] = 'new')和直接修改数组长度(如 arr.length = 0)。Vue 2 对数组方法(如 push, splice)做了特殊处理,但原生操作无效。
  • 初始化时递归遍历所有嵌套属性,性能开销大,尤其对深层对象。

Vue 3 的响应式:基于 Proxy

Vue 3 放弃了 Object.defineProperty,改用 ES6 的 Proxy API 实现响应式。Proxy 可以拦截整个对象的操作,而不仅限于单个属性。

创建响应式对象只需一行:

const reactiveData = reactive({ message: 'Hello' });

内部使用:

function reactive(target) {
  return new Proxy(target, {
    get(obj, key) {
      // 收集依赖
      track(obj, key);
      return obj[key];
    },
    set(obj, key, value) {
      // 触发更新
      trigger(obj, key);
      obj[key] = value;
      return true;
    }
  });
}

相比 Vue 2,Proxy 带来三大优势:

  1. 天然支持动态属性obj.newProp = 'value' 能被自动拦截并响应。
  2. 完整支持数组操作:包括 arr[0] = xarr.length = 0 等原生写法。
  3. 惰性递归:只有访问到嵌套对象时才将其转为响应式,避免无谓性能损耗。

此外,Vue 3 将响应式系统抽离为独立模块 @vue/reactivity,你甚至可以在非 Vue 项目中使用:

import { reactive, effect } from '@vue/reactivity';

const state = reactive({ count: 0 });
effect(() => {
  console.log(state.count); // 自动追踪依赖
});
state.count++; // 输出 1

关键差异对比

以下表格总结了 Vue 2 与 Vue 3 响应式系统的核心区别:

特性 Vue 2 (Object.defineProperty) Vue 3 (Proxy)
新增属性是否响应 ❌ 需 Vue.set ✅ 自动响应
删除属性是否响应 ❌ 需 Vue.delete ✅ 自动响应
数组索引赋值 ❌ 不响应 ✅ 响应
监听整个对象 ❌ 只能逐属性监听 ✅ 完整拦截
性能(深层对象) ❌ 初始化全递归 ✅ 按需代理
浏览器兼容性 ✅ 支持 IE9+ ❌ 仅现代浏览器(不支持 IE)

如何选择?

如果你的项目需要支持 IE 浏览器,只能用 Vue 2,并严格遵守其响应式限制(如避免直接添加属性)。

如果目标环境是现代浏览器,Vue 3 是更优解:

  • 编写更自然的 JavaScript,无需额外 API 处理边界情况。
  • 获得更好的性能和内存表现,尤其在大型应用中。
  • 享受 Composition API 带来的逻辑复用便利,其底层正是基于新的响应式系统。

要迁移旧代码,注意检查是否有以下写法:

  • `this.$set(this.obj, 'prop', val)` → 可直接写 `this.obj.prop = val` - `this.arr[index] = newVal` → 在 Vue 3 中已生效,无需 `splice` --- ## 手动触发响应式的正确姿势 即使在 Vue 3 中,也有少数场景需要手动干预: 1. **替换整个对象**: ```js // 错误:失去响应式引用 state.items = newArray; // 正确:保持响应式 state.items = reactive(newArray); ``` 2. **解构响应式对象**: ```js const { count } = state; // count 不再是响应式的 // 应使用 toRefs 保持响应性 import { toRefs } from 'vue'; const { count } = toRefs(state); ``` 3. **异步修改后强制更新**(极罕见): Vue 3 自动批量更新,通常无需手动触发。若遇极端情况,可用 `nextTick` 确保 DOM 更新完成。 --- ## 验证你的代码是否响应 **测试属性是否响应的最简单方法**:在模板或 `computed` 中使用该数据,然后通过控制台修改它。如果视图更新,说明响应式生效。 例如: ```js // 在组件实例上 app.config.globalProperties.$debug = this.someData;

// 控制台执行
$debug.someProp = 'new value';


若页面未刷新,检查:
- 是否在 `data` 中正确定义(Vue 2)
- 是否使用 `reactive` 或 `ref` 包裹(Vue 3)
- 是否意外替换了整个响应式对象

---

```js
// Vue 3 响应式工具速查
import { ref, reactive, isRef, toRef, toRefs, shallowReactive } from 'vue';

// 基础类型用 ref
const count = ref(0); // .value 访问

// 对象用 reactive
const state = reactive({ list: [] });

// 从响应式对象提取单个属性并保持响应
const listRef = toRef(state, 'list');

// 解构多个属性
const { list, user } = toRefs(state);

// 浅层响应(只代理第一层)
const shallow = shallowReactive({ deep: { nested: true } });
// 修改 shallow.deep.nested 不会触发更新

评论 (0)

暂无评论,快来抢沙发吧!

扫一扫,手机查看

扫描上方二维码,在手机上查看本文