Reactive与Ref的故事
Vue 3的两位"响应式英雄":Reactive与Ref的故事
基本介绍:响应式的两种武器
Vue 3提供了两种创建响应式数据的主要API:reactive()
和ref()
。它们像两种不同的魔法工具,各有所长,共同构建Vue的响应式王国。
┌─────────────────────────────────────────────────────────┐
│ Vue 3 响应式API对比 │
├───────────────────┬─────────────────┬───────────────────┤
│ 特性 │ reactive │ ref │
├───────────────────┼─────────────────┼───────────────────┤
│ 适用数据类型 │ 对象类型 │ 任何类型 │
├───────────────────┼─────────────────┼───────────────────┤
│ 访问/修改方式 │ 直接属性访问 │ .value属性 │
├───────────────────┼─────────────────┼───────────────────┤
│ 内部实现 │ Proxy │ 包装对象+Proxy │
├───────────────────┼─────────────────┼───────────────────┤
│ 解构后保持响应性 │ ❌ │ ✅ │
└───────────────────┴─────────────────┴───────────────────┘
生活类比:魔法容器与魔法盒子
🏰 想象一个奇幻世界的故事:
-
reactive()
是一座"魔法城堡":- 城堡里的每个房间都被施了魔法(对象的每个属性都是响应式的)
- 你可以直接进入任何房间(直接访问对象属性)
- 但是,如果你试图把房间拆下来带走(解构属性),魔法就会消失
- 只有完整的建筑物才能保持魔法(整个对象才有响应性)
-
ref()
是一个"魔法盒子":- 盒子可以装任何东西(原始值或对象)
- 必须打开盒子才能看到里面的东西(通过
.value
访问) - 盒子本身带着魔法标记,无论放在哪里都能被追踪(引用传递保持响应性)
- 即使从大包裹中取出来(从对象解构),盒子仍然保持魔法效果
1. Reactive:对象的响应式转换器
reactive()
将一个普通对象转换为响应式对象,使用ES6的Proxy实现深层响应式,适用于复杂数据结构。
实现流程
┌──────────────────────┐
│ 调用reactive(obj) │
└──────────┬───────────┘│▼
┌──────────────────────┐ ┌───────────────────┐
│ 检查obj是否为对象 │──否──────▶│ 返回原始值 │
└──────────┬───────────┘ └───────────────────┘│是▼
┌──────────────────────┐ ┌───────────────────┐
│ 检查缓存中是否已存在 │──是──────▶│ 返回现有响应式对象 │
└──────────┬───────────┘ └───────────────────┘│否▼
┌──────────────────────┐
│ 创建Proxy包装对象 │
└──────────┬───────────┘│▼
┌──────────────────────┐
│处理getter/setter拦截 │
└──────────┬───────────┘│▼
┌──────────────────────┐
│在getter中收集依赖 │
└──────────┬───────────┘│▼
┌──────────────────────┐
│在setter中触发更新 │
└──────────┬───────────┘│▼
┌──────────────────────┐
│ 返回Proxy对象 │
└──────────────────────┘
代码示例
import { reactive, watchEffect } from 'vue'// 创建响应式对象
const user = reactive({name: '张三',age: 30,address: {city: '北京',district: '朝阳区'}
})// 直接访问和修改
console.log(user.name) // 输出: 张三
user.age = 31 // 直接修改,会触发更新// 监听响应式变化
watchEffect(() => {console.log(`${user.name}今年${user.age}岁,住在${user.address.city}`)
})// 深层嵌套属性也是响应式的
user.address.city = '上海' // 会触发上面的watchEffect重新执行// 🚫 解构后会失去响应性
const { name, age } = user
console.log(name) // 张三
// 修改解构出的变量不会触发更新
age = 32 // watchEffect不会重新执行
2. Ref:任意值的响应式包装器
ref()
可以将任何类型的值(包括原始类型)包装成响应式对象,通过.value
访问和修改内部值,在模板中会自动解包。
实现流程
┌──────────────────────┐
│ 调用ref(value) │
└──────────┬───────────┘│▼
┌──────────────────────┐
│ 创建RefImpl对象 │
└──────────┬───────────┘│▼
┌──────────────────────┐
│内部维护_value属性 │
└──────────┬───────────┘│▼
┌──────────────────────┐ ┌───────────────────────┐
│value是对象吗? │──是───▶│将value转换为reactive │
└──────────┬───────────┘ └───────────────────────┘│否▼
┌──────────────────────┐
│定义value的getter/ │
│setter进行依赖收集 │
└──────────┬───────────┘│▼
┌──────────────────────┐
│返回包含.value的对象 │
└──────────────────────┘
代码示例
import { ref, watchEffect } from 'vue'// 创建基本类型的响应式引用
const count = ref(0)
const message = ref('Hello')
const isActive = ref(true)// 必须通过.value访问和修改
console.log(count.value) // 输出: 0
count.value++ // 通过.value修改// 对象类型也可以使用ref
const user