vue3中ref和reactive的区别举例详解
作者:Hopebearer_
ref和reactive是Vue3中构建响应式数据的两大核心 API,虽然它们在使用上相似,但底层机制和适用场景有明显不同,这篇文章主要介绍了vue3中ref和reactive区别的相关资料,文中通过代码介绍的非常详细,需要的朋友可以参考下
一、核心机制对比
特性 | ref | reactive |
---|---|---|
包装对象 | RefImpl类实现 | Proxy代理对象 |
响应式原理 | 通过 .value 的 getter/setter 拦截 | 深度代理对象的属性访问 |
数据类型支持 | 任意类型(推荐基本类型) | 仅对象/数组/集合类型 |
深层响应式 | 自动展开(对象会转为 reactive) | 默认深层响应 |
模板自动解包 | 支持(顶层属性自动解包) | 不支持(需保持对象引用) |
TS类型推断 | Ref 类型包裹 | 原始类型推断 |
二、底层实现剖析
1. reactive简单实现
const reactive = (target) => { return new Proxy(target, { get(target, key) { // 依赖收集 track(target, key) return Reflect.get(target, key) }, set(target, key, value, receiver) { const oldValue = target[key] const result = Reflect.set(target, key, value, receiver) if (oldValue !== value) { // 触发更新 trigger(target, key) } return result } // 其他拦截操作... }) }
- 依赖收集(Track):
- 通过 track 函数将当前正在执行的副作用(effect)与目标对象的属性关联。
- 使用 WeakMap 存储依赖关系,结构为:
targetMap = WeakMap({ target: Map({ key: Set(effect1, effect2, ...) }) });
- 触发更新(Trigger):
- 当属性变化时,通过 trigger 函数找到所有关联的副作用并执行。
2. ref 实现原理
class RefImpl { constructor(value) { this._value = isObject(value) ? reactive(value) : value; this.dep = new Set(); // 依赖集合 } get value() { track(this.dep); // 依赖收集 return this._value; } set value(newVal) { if (this._value !== newVal) { this._value = isObject(newVal) ? reactive(newVal) : newVal; trigger(this.dep); // 触发更新 } } } const ref = (value) => new RefImpl(value);
- 依赖收集与触发更新:
- 每个 ref 实例内部维护一个 dep 集合(类似 reactive 的依赖管理)。
- 当通过 .value 访问时,触发 track;修改时触发 trigger。
- 关键设计:
- 值类型包装: 通过 .value 访问值,解决基本类型无法被 Proxy 直接代理的问题。
- 自动解包: 在模板中使用 ref 时,Vue 会自动解包(无需写 .value)。
三、实战场景对比
1. 基本类型处理
// 正确用法 const count = ref(0) // ✅ 自动类型推断为 Ref<number> // 错误尝试 const count = reactive(0) // ❌ 参数必须是对象类型
2. 对象类型处理
// ref 处理对象(自动解包) const user = ref({ name: 'John', address: { city: 'New York' } }) // 等效于 reactive 写法 const user = reactive({ name: 'John', address: reactive({ city: 'New York' }) }) // 访问方式对比 console.log(user.value.name) // ref 需要 .value console.log(user.name) // reactive 直接访问
3. 数组处理
// ref 数组 const listRef = ref([1, 2, 3]) listRef.value.push(4) // ✅ 正确修改方式 // reactive 数组 const listReactive = reactive([1, 2, 3]) listReactive.push(4) // ✅ 直接操作
四、高级特性差异
1. 响应式丢失问题
// reactive 的解构问题 const state = reactive({ count: 0 }) const { count } = state // ❌ 失去响应性 // ref 的 解构 const countRef = ref(0) const count = countRef // ❌ 仍需通过 .value 访问 // 正确解构方式(reactive) const state = reactive({ count: 0 }) const countRef = toRef(state, 'count') // ✅ 保持响应
2. 类型替换场景
// ref 允许整体替换 const data = ref({ items: [] }) data.value = fetchData() // ✅ 响应式更新 // reactive 需保持引用 const data = reactive({ items: [] }) Object.assign(data, fetchData()) // ✅ 正确方式 data = fetchData() ❌ 破坏响应性
五、性能对比分析
操作 | ref(基本类型) | reactive(对象) | 差异原因 |
---|---|---|---|
创建速度 | ★★★★☆ | ★★★☆☆ | Proxy初始化成本较高 |
属性访问 | ★★★★☆ | ★★★☆☆ | Proxy拦截带来额外开销 |
深层监听 | ★☆☆☆☆ | ★★★★★ | Proxy自动深度代理 |
内存占用 | 较低 | 较高 | Proxy对象占用更多内存 |
六、最佳实践
1. 组合式函数规范
// 推荐返回 ref 保持灵活性 function useCounter(initial = 0) { const count = ref(initial) return { count } } // 复杂状态使用 reactive + toRefs function useUser() { const state = reactive({ name: '', age: '' }) return { ...toRefs(state) } }
2. 表单处理策略
// 复杂表单使用 reactive const form = reactive({ username: '', password: '', preferences: { theme: 'light', notifications: true } }) // 单个表单字段使用 ref const searchQuery = ref('')
七、特殊场景处理
1. DOM 引用
获取元素对象或者组件对象只能使用 ref ,reactive无法处理 DOM 引用
<script setup> const inputRef = ref(null) </script> <templete> <div ref="inputRef"></div> </templete>
2. 第三方库集成
// 需要保持引用的场景 const chartInstance = ref(null) // 响应式配置对象 const options = reacitve({ title: { text: '热销' }, series: [ ... ] })
八、总结选择策略
1. 优先使用 ref 的场景
- 基本类型值(string/number/boolean)
- 需要模板自动解包
- 可能被整体替换的对象
- 需要传递给composable函数的参数
2. 优先使用 reactive 的场景
- 复杂嵌套对象
- 需要深度响应式监听
- 表单/配置对象等结构化数据
- 需要直接操作集合类型(Map/Set)
3. 混合使用
3.1 reactive 嵌套 ref
const state = reactive({ count: ref(0), // 基础类型 ref ,自动解包 user: ref<User>({ name: '' }) // 对象类型 ref ,自动解包 })
- 行为特点: Vue 会自动解包嵌套在 reactive 中的 ref,访问时无需 .value
state.count++ // 直接操作数字(等价于 state.count.value++) state.user.name = 'john' // 直接操作对象(等价于 state.user.value.name)
- 适用场景
- 混合响应式类型: 当 reactive 对象需要包含基础类型 + 对象类型的混合数据时,用 ref 统一包裹,享受自动解包特性。
- 外部数据注入: 当某个属性需要从外部接收 ref 类型时(如组合式函数返回的 ref),直接将其嵌入 reactive 对象。
3.2 ref 嵌套 reactive
const complexRef = ref({ data: reactive({ /*...*/ }), // 嵌套 reactive 对象 status: 'loading' // 普通属性(非响应式) })
- 行为特点:
- 层级访问: 需要 .value 访问容器内内容
- 响应式范围:
- complexRef.value.data 是响应式对象(reactive 创建)
- complexRef.value.status 是普通字符串(非响应式)
complexRef.value.data.key = "value"; // 直接修改 reactive 对象 complexRef.value.status = "success"; // 修改普通属性(非响应式)
- 适用场景:
- 整体替换响应式对象 :如果需要完全替换整个响应式对象,用 ref 包裹可以保持引用。
- 组合非响应式数据: 当需要混合响应式和非响应式数据时,ref 作为容器更灵活。
complexRef.value = { // 替换整个对象(触发响应式更新) data: reactive({ /*...*/ }), status: 'success' };
总结
到此这篇关于vue3中ref和reactive的区别详解的文章就介绍到这了,更多相关vue3中ref和reactive区别内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!