【vue】通俗易懂的剖析vue3的响应式原理

要搞懂Vue3的响应式原理,咱们先抛开专业术语,用奶茶店接单的生活化例子打底,再拆解核心逻辑,最后用简单代码模拟,保证一看就懂。

核心目标:数据变,页面/逻辑自动更

Vue3响应式的本质就是:当你修改数据时,所有用到这个数据的地方(比如页面显示、计算属性、watch)会自动更新

就像奶茶店:

  • 顾客(数据)点了「珍珠奶茶」(数据值);
  • 后厨(页面/计算属性)要按这个要求备料;
  • 顾客改要求(改数据)为「芋圆奶茶」,后厨不用你喊,自动换料(自动更新)。

第一步:给数据装「管家」------ Proxy代理

Vue3之所以能"感知"数据变化,核心是给数据套了一层「代理(Proxy)」,这个代理就像奶茶店的前台管家:不管是"看数据"(读)还是"改数据"(写),都必须经过它。

对比Vue2(为啥换Proxy?)

Vue2用的是Object.defineProperty,只能监听对象的已有属性,比如:

  • 改数组下标(arr[0] = 1)监听不到;
  • 给对象加新属性(obj.newKey = 1)监听不到。

而Proxy是"全方位监听",能覆盖对象/数组的所有操作(新增、删除、改下标都能感知)。

通俗理解Proxy

假设你有个数据:

js 复制代码
const data = { toppings: '珍珠' }

给它套上Proxy后,就像这样:

js 复制代码
// 管家(Proxy)
const proxyData = new Proxy(data, {
  // 读数据时触发(比如页面渲染用data.toppings)
  get(target, key) {
    console.log('有人看了', key, '的值');
    return target[key]; // 把值返回
  },
  // 改数据时触发(比如data.toppings = '芋圆')
  set(target, key, value) {
    console.log('有人改了', key, '的值,新值是', value);
    target[key] = value; // 把新值存进去
    return true;
  }
});

此时你操作proxyData.toppings,管家都会立刻知道:

js 复制代码
console.log(proxyData.toppings); // 打印:有人看了 toppings 的值 → 珍珠
proxyData.toppings = '芋圆';     // 打印:有人改了 toppings 的值,新值是 芋圆

第二步:记下来「谁用到了数据」------ 依赖收集

管家知道了"谁看/改数据"还不够,得记下来:哪些地方用到了这个数据(比如页面渲染、计算属性),这些"地方"就是「依赖」。

依赖收集的时机

只有当「读数据」时(比如页面首次渲染、计算属性执行),管家才会收集依赖------就像顾客点单时,管家把"顾客A要珍珠奶茶"记在订单本上。

依赖存哪?(简单版容器)

Vue3用三层容器存依赖,咱们简化理解:

复制代码
targetMap(WeakMap)→ 存「响应式对象」对应的依赖表
  └ depsMap(Map)→ 存「对象属性」对应的依赖集合
    └ dep(Set)→ 存「用到这个属性的所有副作用函数」
  • 副作用函数:凡是用到响应式数据的函数(比如页面渲染函数、effectwatch回调),执行它会产生"副作用"(比如改变页面)。
  • 用Set是为了避免重复:比如同一个数据在页面用了两次,只需要更新一次。

第三步:数据变了,喊「依赖」更新------ 触发更新

当你改数据时(管家的set触发),管家会翻订单本,找到所有用到这个数据的依赖,挨个通知它们"数据变了,快更!"------就像顾客改配料,管家喊后厨:"A和B的订单都换成芋圆!"。

完整流程(奶茶店版)

  1. 初始化 :给{ toppings: '珍珠' }套上Proxy管家;
  2. 首次渲染 :页面要显示{``{ toppings }},读toppings → 管家触发get → 把"页面渲染函数"记到toppings的依赖集合里;
  3. 修改数据 :改toppings = '芋圆' → 管家触发set → 找到依赖集合里的"页面渲染函数" → 执行函数 → 页面更新为"芋圆"。

补充:Ref是干啥的?

Proxy只能代理对象/数组 ,没法代理字符串、数字等基本类型(比如let age = 18)。

所以Vue3搞了ref:把基本类型包成「带value属性的对象」,再代理这个对象的value属性。

js 复制代码
const age = ref(18); // 实际是 { value: 18 }
age.value = 20; // 改value才会触发更新

模板里不用写.value,是因为Vue自动帮你"解包"了(比如<div>{``{ age }}</div>)。

用代码模拟Vue3响应式核心

下面的代码去掉了Vue3的复杂逻辑,只保留核心,能直观看到"收集依赖→触发更新"的过程:

js 复制代码
// 1. 存储依赖的容器:targetMap → depsMap → dep
const targetMap = new WeakMap();

// 2. 收集依赖(track:追踪)
function track(target, key) {
  // 找当前对象的依赖表
  let depsMap = targetMap.get(target);
  if (!depsMap) {
    depsMap = new Map();
    targetMap.set(target, depsMap);
  }
  // 找当前属性的依赖集合
  let dep = depsMap.get(key);
  if (!dep) {
    dep = new Set(); // Set避免重复依赖
    depsMap.set(key, dep);
  }
  // 把当前的副作用函数加入依赖
  dep.add(activeEffect);
}

// 3. 触发依赖(trigger:触发)
function trigger(target, key) {
  const depsMap = targetMap.get(target);
  if (!depsMap) return;
  const dep = depsMap.get(key);
  if (dep) {
    // 执行所有依赖的副作用函数
    dep.forEach(effect => effect());
  }
}

// 4. 全局变量:当前正在执行的副作用函数
let activeEffect;

// 5. 注册副作用函数(比如页面渲染、watch)
function effect(fn) {
  activeEffect = fn;
  fn(); // 执行一次,触发track收集依赖
  activeEffect = null;
}

// 6. 模拟reactive:创建响应式对象
function reactive(target) {
  return new Proxy(target, {
    get(target, key) {
      const res = Reflect.get(target, key);
      track(target, key); // 读数据时收集依赖
      return res;
    },
    set(target, key, value) {
      const res = Reflect.set(target, key, value);
      trigger(target, key); // 改数据时触发更新
      return res;
    }
  });
}

// 测试:模拟页面渲染
const data = reactive({ toppings: '珍珠' });

// 注册副作用函数(模拟页面渲染)
effect(() => {
  console.log('页面更新:奶茶配料=' + data.toppings);
});

// 改数据,触发更新
data.toppings = '芋圆'; // 打印:页面更新:奶茶配料=芋圆
data.toppings = '椰果'; // 打印:页面更新:奶茶配料=椰果

关键总结

Vue3响应式的核心就3步:

  1. 代理拦截:用Proxy包裹数据,拦截读(get)和写(set);
  2. 收集依赖:读数据时(get),把用到数据的副作用函数记下来;
  3. 触发更新:改数据时(set),执行所有记下来的副作用函数。

额外补充:

  • reactive:处理对象/数组,默认深响应(嵌套对象也能监听);
  • ref:处理基本类型,本质是代理{ value: 原值 }
  • shallowReactive/shallowRef:浅响应,只监听第一层属性;
  • WeakMap:依赖容器用它是为了内存友好(数据销毁后自动回收依赖,不泄漏)。
相关推荐
wordbaby6 小时前
macOS ⇄ Android 局域网无线传输 APK 终极方案
前端
LYFlied6 小时前
【一句话概括】前端项目包管理器怎么选?
前端·npm·pnpm·yarn
Sui_Network6 小时前
Sui 主网升级至 V1.61.2
大数据·前端·人工智能·深度学习·区块链
哟哟耶耶6 小时前
css-Echarts图表tooltip / label文本过长 超出屏幕边缘或容器范围
前端·javascript·echarts
郑州光合科技余经理6 小时前
解决方案:全球化时代下的海外版外卖系统
大数据·开发语言·前端·javascript·人工智能·架构·php
qq_172805596 小时前
Modbus数据采集 Web 平台介绍
前端
jinxinyuuuus6 小时前
Wallpaper Generator:前端性能优化、UI状态管理与实时渲染的用户体验
前端·ui·性能优化
吃炸鸡的前端6 小时前
tailwindcss v4的基础使用
前端·css
smileNicky6 小时前
分组拖动排序功能全流程实现(前端Sortable.js + 后端Java批量更新)
java·前端·javascript