响应式原理
字数: 0 字 时长: 0 分钟
简介
响应式是 Vue 的核心:数据变了,视图自动更新。Vue2 使用 Object.defineProperty 实现,Vue3 则改用 Proxy 重写了整个响应式系统。这篇笔记记录 Vue3 响应式的实现原理和 ref、reactive 的使用区别,面试必考。
Vue2 与 Vue3 的实现区别
- Vue2:通过
Object.defineProperty劫持对象每个属性的getter/setter。缺点是:- 无法检测对象属性的新增和删除(需要用
Vue.set/Vue.delete)。 - 无法直接检测数组下标修改和
length变化(所以重写了数组的 7 个方法)。 - 初始化时需要递归遍历所有属性,对象层级深时性能差。
- 无法检测对象属性的新增和删除(需要用
- Vue3:通过
Proxy代理整个对象,配合Reflect操作源对象。优点是:- 可以监听属性的新增、删除。
- 可以监听数组下标和
length的变化。 - 惰性劫持,访问到某一层才递归代理,性能更好。
Proxy 的基本用法
js
const obj = { name: "卡比", age: 18 };
const proxy = new Proxy(obj, {
get(target, key) {
console.log(`读取了 ${key}`);
return Reflect.get(target, key);
},
set(target, key, value) {
console.log(`设置了 ${key} = ${value}`);
return Reflect.set(target, key, value);
},
});
proxy.name; // 读取了 name
proxy.age = 20; // 设置了 age = 20响应式的核心流程
Vue3 的响应式可以概括为三个角色:
- reactive:用
Proxy包装数据,在get中收集依赖(track),在set中触发更新(trigger)。 - effect:依赖收集的载体,组件的渲染、computed、watch 本质上都是 effect。
- 依赖关系:每个响应式属性维护一个
dep(依赖集合),记录哪些 effect 用到了它。
js
// 一个极简版的响应式实现思路
let activeEffect = null;
function effect(fn) {
activeEffect = fn;
fn(); // 执行一次,触发 get 收集依赖
activeEffect = null;
}
function reactive(target) {
return new Proxy(target, {
get(obj, key) {
track(obj, key); // 收集依赖:把 activeEffect 存起来
return Reflect.get(obj, key);
},
set(obj, key, value) {
Reflect.set(obj, key, value);
trigger(obj, key); // 触发更新:执行收集到的 effect
return true;
},
});
}注意
Proxy 是 ES6 新增的语法,无法被 polyfill,这也是 Vue3 不支持 IE11 的根本原因。
ref 和 reactive 的区别
| 对比项 | ref | reactive |
|---|---|---|
| 支持类型 | 任意类型(基本类型 + 对象) | 只支持对象类型 |
| 访问方式 | 需要通过 .value | 直接访问 |
| 模板中使用 | 自动解包,不需要 .value | 直接使用 |
| 解构 | 配合 toRefs 保持响应式 | 直接解构会丢失响应式 |
vue
<script setup>
import { ref, reactive } from "vue";
const count = ref(0); // 基本类型用 ref
const state = reactive({ name: "卡比" }); // 对象用 reactive
count.value++; // js 中访问 ref 要加 .value
state.name = "卡比兽";
</script>
<template>
<!-- 模板中 ref 自动解包 -->
<div>{{ count }}</div>
</template>reactive 的两个坑
- 不能直接替换整个对象,会丢失响应式:
js
let state = reactive({ count: 0 });
state = reactive({ count: 1 }); // ❌ 旧对象的响应式连接断了
state.count = 1; // ✅ 正确姿势:修改属性- 解构会丢失响应式,需要用
toRefs:
js
const state = reactive({ name: "卡比", age: 18 });
const { name } = state; // ❌ name 不再是响应式
const { name } = toRefs(state); // ✅ 保持响应式最后更新于: 2026/8/5 15:00:00