【Vue3响应式原理深度解析:从Proxy到依赖收集】

本文将深入剖析Vue3响应式系统的实现原理,帮助你理解Proxy、依赖收集、派发更新的核心机制,提升Vue开发能力。

📋 目录


一、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响应式系统的核心要点:

  1. Proxy优于defineProperty:支持新增/删除属性、数组索引
  2. 依赖收集机制:track收集、trigger派发
  3. ref vs reactive:基本类型用ref,对象用reactive
  4. 性能优化:shallowReactive、markRaw等API
  5. 自定义响应式:customRef实现特殊需求

理解响应式原理,能帮助你写出更高效的Vue代码,避免常见的响应式陷阱。


💬 如果这篇文章对你有帮助,欢迎点赞收藏!有问题欢迎在评论区讨论~

相关推荐
Rhys..2 小时前
js-箭头函数
开发语言·javascript·ecmascript
资深低代码开发平台专家2 小时前
厌倦JavaScript 框架桎梏?Still.js:用原生之力,解遗留系统之困
开发语言·javascript·ecmascript
纟 冬2 小时前
Flutter & OpenHarmony 运动App运动目标设定组件开发
开发语言·javascript·flutter
2501_944446002 小时前
Flutter&OpenHarmony应用内导航与路由管理
开发语言·javascript·flutter
小徐不会敲代码~2 小时前
Vue3 学习 5
前端·学习·vue
_Kayo_2 小时前
vue3 状态管理器 pinia 用法笔记1
前端·javascript·vue.js
How_doyou_do2 小时前
工程级前端智能体FrontAgent
前端
2501_946233892 小时前
Flutter与OpenHarmony Tab切换组件开发详解
android·javascript·flutter
daols882 小时前
vue 甘特图 vxe-gantt table 可视化依赖线的使用,可视化拖拽创建连接线的用法
vue.js·甘特图·vxe-table