Vue 3 的响应式原理

Vue 3 的响应式原理可以比喻为"智能监控系统":当数据变化时,它能自动追踪依赖关系并触发更新。以下是通俗解释和核心机制:


一、核心原理:Proxy 代理

Vue 3 的响应式系统基于 JavaScript 的 Proxy 对象实现(Vue 2 使用 Object.defineProperty)。
Proxy 的优势

  • 支持动态新增属性 (无需 Vue.set
  • 支持数组索引和长度变化
  • 性能更好(无需递归遍历对象)

二、实现步骤:如何让数据"活"起来?

1. 创建响应式对象

reactive() 包裹普通对象,返回一个 Proxy 代理对象:

javascript 复制代码
import { reactive } from 'vue';

const state = reactive({
  count: 0,
  list: ['Apple', 'Banana']
});

Proxy 的拦截行为

  • 当你访问 state.countget 操作)→ 收集依赖(记录谁在用这个值)。
  • 当你修改 state.countset 操作)→ 触发更新(通知依赖它的地方更新)。

2. 依赖收集(Tracking Dependencies)

当模板或计算属性访问响应式数据时,Vue 会记录这个关系:

vue 复制代码
<template>
  <div>{{ state.count }}</div>  <!-- 访问 state.count → 建立依赖 -->
</template>

原理

  • 通过 effect 函数(类似"监听器")包裹需要响应式执行的代码。
  • 当代码执行时,所有被访问的响应式属性会自动收集当前 effect 作为依赖。

3. 触发更新(Triggering Updates)

当数据变化时,Proxy 的 set 拦截器会找到所有关联的 effect 并重新执行它们:

javascript 复制代码
state.count = 10; // 触发 set → 通知所有依赖的 effect 更新

三、核心 API 的差异

1. reactive vs ref
  • reactive:针对对象(Object、Array)的深度代理。

    javascript 复制代码
    const obj = reactive({ a: 1 });
  • ref :针对基本类型(string、number、boolean),通过 .value 访问。

    javascript 复制代码
    const count = ref(0);
    console.log(count.value); // 0

    为什么需要 ref

    Proxy 无法直接代理基本类型,ref 将值包装在 { value: ... } 对象中,再对这个对象做响应式处理。


2. effect 函数

Vue 内部通过 effect 实现依赖追踪和更新触发(开发者通常通过 watchcomputed 间接使用):

javascript 复制代码
import { effect } from 'vue';

effect(() => {
  console.log('count变化了:', state.count); // 自动追踪 state.count
});

四、简单实现(极简版)

javascript 复制代码
// 极简响应式系统
function reactive(obj) {
  return new Proxy(obj, {
    get(target, key) {
      track(target, key); // 收集依赖
      return target[key];
    },
    set(target, key, value) {
      target[key] = value;
      trigger(target, key); // 触发更新
      return true;
    }
  });
}

// 依赖收集和触发
let activeEffect = null;
const depsMap = new Map();

function track(target, key) {
  if (activeEffect) {
    let dep = depsMap.get(target)?.get(key);
    if (!dep) {
      dep = new Set();
      depsMap.set(target, (depsMap.get(target) || new Map()).set(key, dep));
    }
    dep.add(activeEffect);
  }
}

function trigger(target, key) {
  const dep = depsMap.get(target)?.get(key);
  dep?.forEach(effect => effect());
}

function effect(fn) {
  activeEffect = fn;
  fn(); // 首次执行以收集依赖
  activeEffect = null;
}

五、关键特点

  1. 嵌套对象自动代理
    reactive 会递归代理对象的所有嵌套属性。

  2. 懒代理

    Vue 3 不会立即代理所有属性,只有被访问到的属性才会被代理(性能优化)。

  3. 数组处理优化

    直接通过索引修改数组或修改 length 属性也能触发更新。


六、开发者注意事项

  1. 避免解构响应式对象

    解构会破坏代理关系,导致失去响应性:

    javascript 复制代码
    const { count } = state; // ❌ 错误!count 不再是响应式的
    const count = ref(state.count); // ✅ 正确
  2. 使用 toRefs 保持响应性

    解构时保持响应式:

    javascript 复制代码
    import { toRefs } from 'vue';
    const { count } = toRefs(state); // 通过 .value 访问

七、总结

Vue 3 的响应式系统像一个"智能摄像头":

  1. 监控数据访问get):记录谁在关注这个数据。
  2. 监控数据修改set):通知所有关注者更新。
  3. 自动化管理依赖:开发者只需关注数据变化,视图自动同步。

通过 Proxy 的强大能力,Vue 3 解决了 Vue 2 的诸多限制(如数组和新增属性的响应问题),同时提升了性能和开发体验。

相关推荐
拾光拾趣录12 分钟前
for..in 和 Object.keys 的区别:从“遍历对象属性的坑”说起
前端·javascript
OpenTiny社区23 分钟前
把 SearchBox 塞进项目,搜索转化率怒涨 400%?
前端·vue.js·github
编程猪猪侠1 小时前
Tailwind CSS 自定义工具类与主题配置指南
前端·css
qhd吴飞1 小时前
mybatis 差异更新法
java·前端·mybatis
YGY Webgis糕手之路1 小时前
OpenLayers 快速入门(九)Extent 介绍
前端·经验分享·笔记·vue·web
患得患失9491 小时前
【前端】【vueDevTools】使用 vueDevTools 插件并修改默认打开编辑器
前端·编辑器
ReturnTrue8681 小时前
Vue路由状态持久化方案,优雅实现记住表单历史搜索记录!
前端·vue.js
UncleKyrie1 小时前
一个浏览器插件帮你查看Figma设计稿代码图片和转码
前端
遂心_1 小时前
深入解析前后端分离中的 /api 设计:从路由到代理的完整指南
前端·javascript·api
你听得到112 小时前
Flutter - 手搓一个日历组件,集成单日选择、日期范围选择、国际化、农历和节气显示
前端·flutter·架构