本文将深入剖析Vue3响应式系统的实现原理,帮助你理解Proxy、依赖收集、派发更新的核心机制,提升Vue开发能力。
📋 目录
- 一、Vue3响应式概述
- [二、Proxy vs Object.defineProperty](#二、Proxy vs Object.defineProperty)
- 三、响应式核心实现
- 四、依赖收集与派发更新
- 五、ref与reactive深度对比
- 六、响应式进阶技巧
一、Vue3响应式概述
1.1 什么是响应式
响应式是Vue的核心特性,当数据变化时,视图自动更新。
javascript
// 响应式的魔法
const state = reactive({ count: 0 });
// 当count变化时,使用它的组件自动重新渲染
state.count++; // 视图自动更新
1.2 Vue3响应式架构
Vue3响应式系统
├── @vue/reactivity(核心包)
│ ├── reactive() - 对象响应式
│ ├── ref() - 基本类型响应式
│ ├── computed() - 计算属性
│ ├── watch() - 侦听器
│ └── effect() - 副作用函数
├── 依赖收集(Track)
│ └── 建立数据与effect的关联
└── 派发更新(Trigger)
└── 数据变化时触发effect重新执行
二、Proxy vs Object.defineProperty
2.1 Vue2的局限性
❌ Vue2响应式问题
javascript
// Vue2使用Object.defineProperty
const data = { name: 'Vue2' };
Object.defineProperty(data, 'name', {
get() {
console.log('读取name');
return value;
},
set(newVal) {
console.log('设置name');
value = newVal;
}
});
// 问题1:无法检测新增属性
data.age = 18; // ❌ 不会触发响应式
// 问题2:无法检测数组索引变化
const arr = [1, 2, 3];
arr[0] = 100; // ❌ 不会触发响应式
// 问题3:无法检测数组长度变化
arr.length = 0; // ❌ 不会触发响应式
2.2 Vue3的Proxy优势
✅ Vue3完美解决
javascript
// Vue3使用Proxy
const data = reactive({ name: 'Vue3' });
// ✅ 可以检测新增属性
data.age = 18; // 自动响应式
// ✅ 可以检测数组索引变化
const arr = reactive([1, 2, 3]);
arr[0] = 100; // 自动响应式
// ✅ 可以检测数组长度变化
arr.length = 0; // 自动响应式
// ✅ 可以检测属性删除
delete data.name; // 自动响应式
2.3 对比总结
| 特性 | Vue2 (defineProperty) | Vue3 (Proxy) |
|---|---|---|
| 新增属性 | ❌ 需要Vue.set | ✅ 自动检测 |
| 删除属性 | ❌ 需要Vue.delete | ✅ 自动检测 |
| 数组索引 | ❌ 不支持 | ✅ 自动检测 |
| 数组长度 | ❌ 不支持 | ✅ 自动检测 |
| 性能 | 递归遍历 | 惰性代理 |
| 兼容性 | IE9+ | 不支持IE |
三、响应式核心实现
3.1 手写简易reactive
javascript
// 存储依赖关系的WeakMap
const targetMap = new WeakMap();
let activeEffect = null;
// reactive实现
function reactive(target) {
return new Proxy(target, {
get(target, key, receiver) {
const result = Reflect.get(target, key, receiver);
// 依赖收集
track(target, key);
// 深层响应式
if (typeof result === 'object' && result !== null) {
return reactive(result);
}
return result;
},
set(target, key, value, receiver) {
const oldValue = target[key];
const result = Reflect.set(target, key, value, receiver);
// 值变化时派发更新
if (oldValue !== value) {
trigger(target, key);
}
return result;
},
deleteProperty(target, key) {
const hadKey = Object.prototype.hasOwnProperty.call(target, key);
const result = Reflect.deleteProperty(target, key);
if (hadKey && result) {
trigger(target, key);
}
return result;
}
});
}
3.2 依赖收集track
javascript
function track(target, key) {
if (!activeEffect) return;
// 获取target对应的depsMap
let depsMap = targetMap.get(target);
if (!depsMap) {
targetMap.set(target, (depsMap = new Map()));
}
// 获取key对应的dep集合
let dep = depsMap.get(key);
if (!dep) {
depsMap.set(key, (dep = new Set()));
}
// 添加当前effect
dep.add(activeEffect);
}
3.3 派发更新trigger
javascript
function trigger(target, key) {
const depsMap = targetMap.get(target);
if (!depsMap) return;
const dep = depsMap.get(key);
if (!dep) return;
// 执行所有收集的effect
dep.forEach(effect => {
if (effect.scheduler) {
effect.scheduler();
} else {
effect();
}
});
}
3.4 effect副作用函数
javascript
function effect(fn, options = {}) {
const effectFn = () => {
activeEffect = effectFn;
const result = fn();
activeEffect = null;
return result;
};
effectFn.scheduler = options.scheduler;
// 立即执行一次,收集依赖
if (!options.lazy) {
effectFn();
}
return effectFn;
}
// 使用示例
const state = reactive({ count: 0 });
effect(() => {
console.log('count变化了:', state.count);
});
state.count++; // 输出: count变化了: 1
state.count++; // 输出: count变化了: 2
四、依赖收集与派发更新
4.1 依赖收集流程图
组件渲染/effect执行
↓
读取响应式数据
↓
触发Proxy的get
↓
执行track()收集依赖
↓
targetMap → depsMap → dep(Set)
↓ ↓ ↓
target key effects
4.2 数据结构详解
javascript
// targetMap: WeakMap<target, depsMap>
// depsMap: Map<key, dep>
// dep: Set<effect>
// 示例
const state = reactive({
name: 'Vue3',
count: 0
});
effect(() => console.log(state.name));
effect(() => console.log(state.count));
// 内部数据结构
targetMap = {
[state]: {
'name': Set([effect1]),
'count': Set([effect2])
}
}
4.3 嵌套effect处理
javascript
// Vue3使用effectStack解决嵌套问题
const effectStack = [];
function effect(fn) {
const effectFn = () => {
// 清除旧依赖
cleanup(effectFn);
// 入栈
effectStack.push(effectFn);
activeEffect = effectFn;
const result = fn();
// 出栈
effectStack.pop();
activeEffect = effectStack[effectStack.length - 1];
return result;
};
effectFn.deps = [];
effectFn();
return effectFn;
}
function cleanup(effectFn) {
effectFn.deps.forEach(dep => {
dep.delete(effectFn);
});
effectFn.deps.length = 0;
}
五、ref与reactive深度对比
5.1 ref实现原理
javascript
function ref(value) {
return new RefImpl(value);
}
class RefImpl {
constructor(value) {
this._value = isObject(value) ? reactive(value) : value;
this.__v_isRef = true;
}
get value() {
track(this, 'value');
return this._value;
}
set value(newVal) {
if (newVal !== this._value) {
this._value = isObject(newVal) ? reactive(newVal) : newVal;
trigger(this, 'value');
}
}
}
5.2 使用场景对比
❌ 错误示例:混淆使用
javascript
// 基本类型用reactive会丢失响应式
let count = reactive(0); // ❌ 错误
count = 1; // 丢失响应式
// 对象用ref需要.value
const user = ref({ name: 'Vue' });
console.log(user.name); // ❌ undefined
console.log(user.value.name); // ✅ 'Vue'
✅ 正确示例:合理选择
javascript
// 基本类型用ref
const count = ref(0);
count.value++; // ✅ 响应式
// 对象用reactive
const user = reactive({
name: 'Vue',
age: 3
});
user.name = 'Vue3'; // ✅ 响应式,无需.value
// 复杂场景:reactive包含ref
const state = reactive({
count: ref(0) // 自动解包,无需.value
});
state.count++; // ✅ 直接使用
5.3 对比总结
| 特性 | ref | reactive |
|---|---|---|
| 适用类型 | 基本类型、对象 | 仅对象 |
| 访问方式 | .value | 直接访问 |
| 解构 | 保持响应式 | ❌ 丢失响应式 |
| 模板中 | 自动解包 | 直接使用 |
| 重新赋值 | ✅ 支持 | ❌ 丢失响应式 |
5.4 toRef与toRefs
javascript
const state = reactive({
name: 'Vue3',
version: 3
});
// ❌ 解构会丢失响应式
const { name, version } = state;
// ✅ 使用toRefs保持响应式
const { name, version } = toRefs(state);
console.log(name.value); // 'Vue3'
// ✅ 单个属性用toRef
const nameRef = toRef(state, 'name');
六、响应式进阶技巧
6.1 shallowReactive与shallowRef
javascript
// 浅层响应式,只有第一层是响应式
const state = shallowReactive({
nested: {
count: 0
}
});
state.nested = { count: 1 }; // ✅ 触发更新
state.nested.count++; // ❌ 不触发更新
// shallowRef
const data = shallowRef({ count: 0 });
data.value.count++; // ❌ 不触发更新
data.value = { count: 1 }; // ✅ 触发更新
6.2 readonly与shallowReadonly
javascript
const original = reactive({ count: 0 });
// 深层只读
const readonlyState = readonly(original);
readonlyState.count++; // ❌ 警告,无法修改
// 浅层只读
const shallowState = shallowReadonly({
nested: { count: 0 }
});
shallowState.nested = {}; // ❌ 警告
shallowState.nested.count++; // ✅ 可以修改
6.3 markRaw跳过响应式
javascript
const rawData = markRaw({
largeList: new Array(10000).fill(0)
});
const state = reactive({
data: rawData // 不会被转为响应式
});
// 适用场景:
// 1. 大型不可变数据
// 2. 第三方库实例
// 3. 不需要响应式的数据
6.4 customRef自定义ref
javascript
// 防抖ref
function useDebouncedRef(value, delay = 300) {
let timeout;
return customRef((track, trigger) => ({
get() {
track();
return value;
},
set(newValue) {
clearTimeout(timeout);
timeout = setTimeout(() => {
value = newValue;
trigger();
}, delay);
}
}));
}
// 使用
const searchText = useDebouncedRef('', 500);
// 输入时自动防抖
watch(searchText, (val) => {
console.log('搜索:', val);
});
6.5 triggerRef手动触发
javascript
const data = shallowRef({ count: 0 });
// 修改深层属性不会触发更新
data.value.count++;
// 手动触发更新
triggerRef(data);
🎯 实战案例:响应式状态管理
javascript
// stores/useCounter.js
import { reactive, computed, watch } from 'vue';
export function useCounter() {
const state = reactive({
count: 0,
step: 1
});
// 计算属性
const doubleCount = computed(() => state.count * 2);
// 方法
const increment = () => {
state.count += state.step;
};
const decrement = () => {
state.count -= state.step;
};
const setStep = (step) => {
state.step = step;
};
// 侦听器
watch(
() => state.count,
(newVal, oldVal) => {
console.log(`count从${oldVal}变为${newVal}`);
// 持久化
localStorage.setItem('count', newVal);
}
);
// 初始化
const savedCount = localStorage.getItem('count');
if (savedCount) {
state.count = Number(savedCount);
}
return {
state,
doubleCount,
increment,
decrement,
setStep
};
}
vue
<!-- 组件中使用 -->
<template>
<div>
<p>Count: {{ state.count }}</p>
<p>Double: {{ doubleCount }}</p>
<button @click="decrement">-</button>
<button @click="increment">+</button>
<input v-model.number="state.step" />
</div>
</template>
<script setup>
import { useCounter } from '@/stores/useCounter';
const { state, doubleCount, increment, decrement } = useCounter();
</script>
📊 响应式性能优化
| 优化策略 | 方法 | 适用场景 |
|---|---|---|
| 避免深层响应式 | shallowReactive | 大型嵌套对象 |
| 跳过响应式 | markRaw | 不变数据、第三方实例 |
| 只读保护 | readonly | 防止意外修改 |
| 延迟计算 | computed | 复杂计算 |
| 防抖更新 | customRef | 频繁输入 |
💡 总结
Vue3响应式系统的核心要点:
- ✅ Proxy优于defineProperty:支持新增/删除属性、数组索引
- ✅ 依赖收集机制:track收集、trigger派发
- ✅ ref vs reactive:基本类型用ref,对象用reactive
- ✅ 性能优化:shallowReactive、markRaw等API
- ✅ 自定义响应式:customRef实现特殊需求
理解响应式原理,能帮助你写出更高效的Vue代码,避免常见的响应式陷阱。
💬 如果这篇文章对你有帮助,欢迎点赞收藏!有问题欢迎在评论区讨论~