Vue 3 响应式系统:Proxy 与 reactive 核心解析(含 ref 对比)

在 Vue 3 开发中,响应式系统是核心特性之一,而 Proxy 和 reactive 则是构建这一系统的关键。很多开发者在实际开发中容易混淆 reactive 与 ref 的使用场景,本文将从底层原理到实战应用,详细解析 Proxy 的作用、reactive 的核心功能,并清晰对比 reactive 与 ref 的差异,附带可直接运行的代码示例,助力大家快速掌握 Vue 3 响应式API的使用技巧。

一、Vue 3 响应式系统基于 Proxy 的底层逻辑

1.1 什么是 Proxy?

Proxy 是 ES6 引入的 JavaScript 内置对象,其核心能力是拦截并自定义对象的基本操作,包括属性的读取、设置、删除、遍历等。简单来说,Proxy 就像一个“数据代理服务器”,所有对原始对象的操作都必须经过这个代理,我们可以在代理中嵌入自定义逻辑,实现对数据的监控与干预。

1.2 Proxy 基础用法演示

通过一个简单示例理解 Proxy 的拦截效果:


// 原始普通对象
const target = { name: '张三', age: 20 }

// 创建 Proxy 代理对象
const proxyTarget = new Proxy(target, {
  // 拦截属性读取操作
  get(target, property) {
    console.log(`读取属性 ${property},值为:${target[property]}`)
    return target[property] // 返回原始属性值
  },
  // 拦截属性设置操作
  set(target, property, value) {
    console.log(`设置属性 ${property},新值为:${value}`)
    target[property] = value // 更新原始属性值
    return true // 表示设置成功
  }
})

// 操作代理对象(而非原始对象)
proxyTarget.name = '李四' // 触发 set 拦截
console.log(proxyTarget.age) // 触发 get 拦截

运行结果:


设置属性 name,新值为:李四
读取属性 age,值为:20

1.3 Vue 3 如何利用 Proxy 实现响应式?

Vue 3 响应式系统的核心就是借助 Proxy 的拦截能力,实现依赖追踪视图自动更新,完整流程可分为两步:

  1. 依赖收集阶段:当组件首次渲染时,Vue 会遍历组件模板中使用的数据,通过 Proxy 的 get 拦截器记录“哪些组件使用了哪些数据”,建立数据与组件的依赖映射关系。

  2. 更新触发阶段:当通过 Proxy 代理对象修改数据时,set 拦截器会被触发,Vue 会根据之前收集的依赖关系,通知所有使用该数据的组件重新渲染,实现视图与数据的同步。

注意:Proxy 只能代理对象或数组等引用类型,无法直接代理数字、字符串等基本类型,这也是 ref 存在的核心原因。

二、reactive 的核心作用与使用指南

2.1 reactive 是什么?

reactive 是 Vue 3 Composition API 提供的核心响应式 API,其作用是将普通对象或数组转换为响应式对象。经过 reactive 处理后,对象的属性变更会自动触发依赖组件的重新渲染。

2.2 reactive 基础用法

使用 reactive 创建响应式对象后,可直接修改属性实现响应式更新:


import { reactive } from 'vue'

// 1. 将普通对象转换为响应式对象
const user = reactive({
  name: '张三',
  age: 28,
  hobbies: ['阅读', '跑步', '编程']
})

// 2. 直接修改属性(无需 .value)
const updateUser = () => {
  user.name = '李四' // 修改字符串属性
  user.age++ // 修改数字属性
  user.hobbies.push('旅行') // 修改数组
}

2.3 reactive 工作原理拆解

reactive 的内部实现本质是对 Proxy 的封装,核心流程如下:

  1. 接收一个普通对象(或数组)作为参数;

  2. 判断参数是否为引用类型,若不是则给出警告;

  3. 为该对象创建 Proxy 代理,并配置 get、set 等拦截器;

  4. 返回 Proxy 代理对象,后续对该对象的操作都会触发响应式逻辑。

2.4 reactive 使用注意事项

使用 reactive 时,有一个关键禁忌:不能直接替换整个响应式对象,否则会丢失响应式能力。


const user = reactive({ name: '张三' })

// 错误做法:直接替换对象,代理关系失效
user = { name: '李四' } // 此时 user 不再是响应式对象

// 正确做法:修改对象属性
user.name = '李四'

// 若需整体更新,可嵌套一层对象
const state = reactive({
  user: { name: '张三' }
})
// 这样替换就不会丢失响应式
state.user = { name: '李四' }

三、reactive 与 ref 的核心区别及选型指南

对比维度reactiveref
适用数据类型仅支持对象、数组等引用类型支持所有类型(基本类型、引用类型均可)
数据访问方式直接访问属性(如 user.name)脚本中需用 .value(如 count.value),模板中可省略
整体替换数据不支持,直接替换会丢失响应式支持(如 count = ref(10))
内部实现逻辑直接对原始对象创建 Proxy 代理将数据包装为 { value: 原始值 } 后,对包装对象创建 Proxy
使用场景复杂对象的属性修改基本类型数据、需整体替换的引用类型

3.1 核心差异对比表

对比维度reactiveref
适用数据类型仅支持对象、数组等引用类型支持所有类型(基本类型、引用类型均可)
数据访问方式直接访问属性(如 user.name)脚本中需用 .value(如 count.value),模板中可省略
整体替换数据不支持,直接替换会丢失响应式支持(如 count = ref(10))
内部实现逻辑直接对原始对象创建 Proxy 代理将数据包装为 { value: 原始值 } 后,对包装对象创建 Proxy
使用场景复杂对象的属性修改基本类型数据、需整体替换的引用类型

3.2 为什么需要 ref?

前文提到,Proxy 无法直接代理基本类型(如 number、string、boolean),而 ref 通过“包装对象”的方式解决了这一问题。例如:


import { ref } from 'vue'

// ref 会将基本类型包装为 { value: 0 } 的对象
const count = ref(0)

// 脚本中需通过 .value 访问和修改
count.value++ // 正确修改方式

// 模板中可直接使用(Vue 会自动解析 .value)
// <p>计数:{{ count }}</p>

对于引用类型,ref 同样适用,其内部会自动调用 reactive 处理:


// 用 ref 包装对象
const user = ref({ name: '张三', age: 28 })

// 修改属性时需通过 .value 访问
user.value.name = '李四'

// 整体替换对象(支持)
user.value = { name: '王五', age: 30 }

3.3 实战选型建议

  1. 基本类型优先用 ref:如数字(计数)、布尔值(开关状态)、字符串(输入框内容)等,直接用 ref 更简洁。

  2. 复杂对象分场景选择
    若仅修改对象属性(如用户信息的姓名、年龄),用 reactive 更直观(无需 .value);

  3. 若需整体替换对象(如接口返回新的用户信息覆盖旧数据),用 ref 更方便。

  4. 团队协作统一风格:若团队成员对两种 API 切换不熟练,可统一用 ref(支持所有场景),降低协作成本。

四、项目实战案例对比

以实际项目中的“菜单折叠状态管理”为例,对比 reactive 与 ref 的实现方式。

4.1 用 ref 实现(项目中常用)

菜单折叠状态是布尔值(基本类型),用 ref 实现更简洁:


<template>
  <el-aside :collapsed="isCollapse">
    <el-button @click="toggleCollapse">切换折叠</el-button>
  </el-aside>
</template>

<script setup>
import { ref } from 'vue'

// 用 ref 管理布尔值状态
const isCollapse = ref(false)

// 修改时需用 .value
const toggleCollapse = () => {
  isCollapse.value = !isCollapse.value
}
</script>

4.2 用 reactive 实现

若用 reactive 实现,需将布尔值包装为对象属性:


<template>
  <el-aside :collapsed="state.isCollapse">
    <el-button @click="toggleCollapse">切换折叠</el-button>
  </el-aside>
</template>

<script setup>
import { reactive } from 'vue'

// 用 reactive 包装对象管理状态
const state = reactive({
  isCollapse: false
})

// 直接修改对象属性
const toggleCollapse = () => {
  state.isCollapse = !state.isCollapse
}
</script>

五、可直接运行的 reactive 演示组件

为了更直观地理解 reactive 的使用,下面提供一个完整的 Vue 组件示例,包含响应式对象的创建、修改、数组操作等场景,可直接复制到项目中运行。


<template>
  <div class="reactive-demo" style="padding: 20px;">
    <h3 style="margin-bottom: 20px;">Reactive 实战演示</h3>

    
    <div style="margin-bottom: 20px; padding: 15px; border: 1px solid #eee;">
      <h4>用户信息</h4>
      <p>姓名:{{ user.name }}</p>
      <p>年龄:{{ user.age }}</p>
      <p>爱好:{{ user.hobbies.join('、') }}</p>
    </div>

    
    <div style="margin-bottom: 20px;">
      <el-button @click="updateName" style="margin-right: 10px;">修改姓名</el-button>
      <el-button @click="addAge" style="margin-right: 10px;">年龄+1</el-button>
      <el-button @click="addHobby" style="margin-right: 10px;">添加爱好</el-button>
      <el-button @click="resetUser">重置信息</el-button>
    </div>

    
    <div style="padding: 15px; border: 1px solid #eee;">
      <h4>ref 与 reactive 类型对比</h4>
      <p>ref 包装数字:{{ refCount }}(类型:{{ typeof refCount }})</p>
      <p>reactive 包装对象:{{ reactiveObj.count }}(类型:{{ typeof reactiveObj }})</p>
    </div>
  </div>
</template>

<script setup>
import { reactive, ref, computed } from 'vue'

// 1. reactive 创建响应式对象
const user = reactive({
  name: '张三',
  age: 28,
  hobbies: ['阅读', '跑步', '编程']
})

// 操作响应式对象的方法
const updateName = () => {
  user.name = '李四' // 直接修改属性
}
const addAge = () => {
  user.age++ // 直接修改数字属性
}
const addHobby = () => {
  user.hobbies.push('旅行') // 直接操作数组
}
const resetUser = () => {
  // 重置时逐个修改属性(避免整体替换)
  user.name = '张三'
  user.age = 28
  user.hobbies = ['阅读', '跑步', '编程']
}

// 2. 与 ref 对比
const refCount = ref(0)
const reactiveObj = reactive({ count: 0 })
</script>

六、总结

Vue 3 响应式系统的核心是基于 Proxy 实现的拦截机制,而 reactive 和 ref 则是封装后的易用 API,二者的核心区别在于适用数据类型和操作方式:

  1. Proxy 是底层基石:负责拦截对象操作,实现依赖追踪和更新触发;

  2. reactive 面向引用类型:直接将对象转换为响应式,适合修改属性的场景;

  3. ref 是通用解决方案:通过包装对象支持所有类型,适合基本类型和需整体替换的场景。

实际开发中无需死记硬背,根据数据类型和业务场景选择即可:基本类型用 ref,复杂对象优先用 reactive(需修改属性)或 ref(需整体替换),团队协作可统一风格提升效率。

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐