在 Vue3 的 Composition API 中,refreactive是实现响应式数据的核心 API,二者都基于 ES6 的Proxy API,但在使用场景和实现逻辑上存在显著差异。本文将从底层原理出发,结合 Vue3 源码片段,详细拆解两者的实现机制,并梳理它们的核心区别,帮助开发者更精准地选择响应式方案。

一、响应式的底层基石:Proxy API

在分析refreactive之前,必须先明确 Vue3 响应式的底层依赖 ——Proxy。与 Vue2 的Object.defineProperty相比,Proxy能直接代理整个对象(包括数组),支持动态新增 / 删除属性,且能拦截更多操作(如deletein等),这也是refreactive实现的核心基础。

Proxy的基本用法如下:

const target = { count: 0 }

const proxy = new Proxy(target, {

 // 拦截属性读取

 get(target, key) {

   track(target, key) // 依赖收集:记录当前使用该属性的副作用函数

   return target[key]

 },

 // 拦截属性修改

 set(target, key, value) {

   target[key] = value

   trigger(target, key) // 触发更新:执行所有依赖该属性的副作用函数

 }

})

其中,track(依赖收集)和trigger(触发更新)是 Vue3 响应式系统的核心逻辑,refreactive都会复用这两个函数,但在代理对象的处理上存在差异。

二、reactive 原理:直接代理对象

reactive的核心作用是将普通对象 / 数组转换为响应式对象,它直接对目标对象进行Proxy代理,不额外包装一层结构。

1. reactive 的源码核心(简化版)

// 存储已代理的对象,避免重复代理

const reactiveMap = new WeakMap()

function reactive(target) {

 // 1. 若目标不是对象/数组,直接返回(reactive不支持基础类型)

 if (typeof target !== 'object' || target === null) {

   return target

 }

 // 2. 若目标已被代理,直接返回缓存的代理对象

 const existingProxy = reactiveMap.get(target)

 if (existingProxy) {

   return existingProxy

 }

 // 3. 创建Proxy代理,拦截get/set等操作

 const proxy = new Proxy(target, {

   get(target, key, receiver) {

     // 特殊处理:如果访问的是内置属性(如__v_isReactive),直接返回标识

     if (key === '__v_isReactive') {

       return true

     }

     // 依赖收集:记录当前副作用函数与target、key的关联

     track(target, key)

     // 递归代理:若属性值是对象,访问时自动转为响应式(深度代理)

     const res = Reflect.get(target, key, receiver)

     return reactive(res) // 关键:深度响应式的实现

   },

   set(target, key, value, receiver) {

     // 先获取旧值,判断是否需要触发更新

     const oldValue = Reflect.get(target, key, receiver)

     const result = Reflect.set(target, key, value, receiver)

    

     // 若值发生变化,触发依赖更新

     if (oldValue !== value) {

       trigger(target, key)

     }

     return result

   },

   // 拦截delete操作(支持删除属性后触发更新)

   deleteProperty(target, key) {

     const hadKey = hasOwn(target, key)

     const result = Reflect.deleteProperty(target, key)

     if (hadKey) {

       trigger(target, key) // 删除属性后触发更新

     }

     return result

   }

 })

 // 4. 缓存代理对象,存入WeakMap

 reactiveMap.set(target, proxy)

 return proxy

}

2. reactive 的核心特性

  • 仅支持对象 / 数组:对基础类型(number、string 等)无效,传入基础类型会直接返回原值;

  • 深度代理:访问对象的嵌套属性时,会自动对嵌套对象执行reactive,实现深度响应式;

  • 直接操作属性:使用时无需额外加.value,如state.count++

  • 弱引用缓存:通过WeakMap存储已代理对象,避免内存泄漏(目标对象被销毁时,缓存自动清除)。

三、ref 原理:包装基础类型的响应式容器

ref的核心作用是为基础类型(如 number、string)提供响应式支持,它通过一个 “容器对象” 包装基础类型,再对容器对象进行Proxy代理,间接实现基础类型的响应式。

1. ref 的源码核心(简化版)

// 存储ref对象的原始值和响应式标识

class RefImpl {

 constructor(rawValue) {

   // 1. 存储原始值(若原始值是对象,先转为reactive)

   this._rawValue = rawValue

   this._value = reactive(rawValue) // 关键:对象类型的ref会复用reactive

   // 2. 标识当前是ref对象

   this.__v_isRef = true

 }

 // 3. 通过getter拦截.value的读取,触发依赖收集

 get value() {

   track(this, 'value') // 依赖收集:关联副作用函数与ref的value属性

   return this._value

 }

 // 4. 通过setter拦截.value的修改,触发更新

 set value(newValue) {

   // 若新值与原始值不同,更新原始值和响应式值

   if (newValue !== this._rawValue) {

     this._rawValue = newValue

     this._value = reactive(newValue) // 新值是对象时,转为reactive

     trigger(this, 'value') // 触发依赖更新

   }

 }

}

// ref函数:创建RefImpl实例

function ref(rawValue) {

 return new RefImpl(rawValue)

}

// 辅助函数:判断是否为ref对象

function isRef(r) {

 return !!(r && r.__v_isRef === true)

}

2. ref 的核心特性

  • 支持所有类型:既可以包装基础类型(number、string),也可以包装对象 / 数组(内部会转为reactive);

  • 需通过.value 访问:由于基础类型无法被Proxy直接代理,需通过容器的value属性间接操作;

  • 自动解包场景

    • 在模板中使用时,无需写.value(Vue 会自动解包),如<div>{{ count }}</div>

    • reactive对象中嵌套 ref 时,访问时会自动解包,如const state = reactive({ count: ref(0) }),使用时可直接state.count++

四、ref 与 reactive 的核心区别

通过原理分析,可从以下 5 个维度清晰区分两者:

对比维度 reactive ref
支持数据类型 仅对象 / 数组(引用类型) 所有类型(基础类型 + 引用类型)
访问方式 直接访问属性(如state.count 需通过.value(如count.value
深度响应式实现 访问嵌套属性时递归代理 包装对象后,内部调用 reactive 实现
自动解包场景 无自动解包需求 模板中、reactive 嵌套时自动解包
使用场景 管理复杂对象 / 数组(如表单数据) 管理基础类型(如单个数字、字符串)

典型使用场景示例

  1. reactive 场景:管理表单数据(复杂对象)
const form = reactive({

 username: '',

 password: ''

})

// 直接操作属性,无需.value

form.username = 'vue-dev'
  1. ref 场景:管理单个基础类型值(如计数器)
const count = ref(0)

// 需通过.value修改

count.value++

// 模板中自动解包:<div>{{ count }}</div>

五、常见疑问解答

  1. 为什么 ref 包装对象时,内部要转为 reactive?

    因为对象本身是引用类型,直接代理容器的value属性无法实现对象内部属性的响应式,所以 ref 内部会调用reactive,将对象转为响应式,确保嵌套属性也能触发更新。

  2. 什么时候该用 ref,什么时候该用 reactive?

  • 若变量是基础类型(如 number、string),直接用 ref;

  • 若变量是复杂对象 / 数组(如表单、列表),优先用 reactive;

  • 若不确定变量类型,或后续可能从基础类型转为对象,用 ref 更灵活。

  1. ref 的.value 会不会忘记写?

    开发中可通过 ESLint 插件(如eslint-plugin-vue)配置规则,检测到 ref 未用.value时自动提示,避免遗漏。

总结

refreactive都是 Vue3 响应式系统的核心 API,底层均基于Proxy和依赖收集机制,但设计思路不同:

  • reactive是 “直接代理”,专为引用类型设计,使用更简洁;

  • ref是 “容器包装”,通过.value间接实现基础类型的响应式,场景更通用。

理解两者的原理和区别后,可根据具体数据类型和使用场景灵活选择,让响应式代码更高效、更易维护。

更多推荐