下面,我们来系统的梳理关于 ref() 与 reactive() 的基本知识点:
一、响应式编程核心概念
1.1 什么是响应式编程?
响应式编程是一种声明式编程范式,它使数据变化能够自动传播到依赖它的代码部分。在 Vue 中,响应式系统实现了:
- 数据驱动视图:数据变化自动更新DOM
- 依赖追踪:自动跟踪数据依赖关系
- 高效更新:最小化不必要的DOM操作
1.2 Vue 响应式系统演进
版本 | 响应式实现 | 特点 |
---|---|---|
Vue 2 | Object.defineProperty | 拦截getter/setter,不支持数组索引变化 |
Vue 3 | Proxy API | 全面拦截对象操作,支持数组/集合类型 |
二、reactive() 深度解析
2.1 基本使用
import { reactive } from 'vue'
const state = reactive({
count: 0,
user: {
name: 'Alice',
profile: {
level: 5
}
}
})
// 修改属性
state.count++ // 触发更新
state.user.name = 'Bob' // 深层响应
2.2 实现原理
2.3 核心特性
- 深层响应:嵌套对象自动转为响应式
- 类型保留:Array/Map/Set等特殊类型保持原型方法
- 引用稳定:reactive() 返回同一个代理对象
const obj = {} const proxy1 = reactive(obj) const proxy2 = reactive(obj) console.log(proxy1 === proxy2) // true
2.4 局限性
// 1. 原始值无法使用
const count = reactive(0) // 无效
// 2. 属性解构丢失响应性
const { count } = state // 非响应式
// 3. 新属性添加需要特殊处理
const state = reactive({})
state.newProp = 'value' // 非响应式
// 正确添加响应式属性
import { set } from 'vue'
set(state, 'newProp', 'value')
三、ref() 深度解析
3.1 基本使用
import { ref } from 'vue'
// 创建ref
const count = ref(0)
const user = ref({ name: 'Alice' })
// 模板中使用
// <div>{{ count }}</div> 自动解包.value
// JS中访问
console.log(count.value) // 0
count.value++ // 触发更新
3.2 实现原理
// 简化的ref实现
function ref(value) {
const refObject = {
get value() {
track(refObject, 'value') // 依赖收集
return value
},
set value(newVal) {
value = newVal
trigger(refObject, 'value') // 触发更新
}
}
return refObject
}
3.3 核心特性
- 包装原始值:使基本类型具有响应性
- 模板自动解包:在模板中无需.value
- 响应式替换:整个对象可替换
const user = ref({ name: 'Alice' }) // 替换整个对象 user.value = { name: 'Bob' } // 保持响应性
3.4 ref 的特殊用法
// DOM元素引用
const inputRef = ref(null)
onMounted(() => {
inputRef.value.focus()
})
// 组件引用
<ChildComponent ref="child" />
const child = ref(null)
child.value.doSomething()
// 函数式ref
<div :ref="(el) => { /* 操作元素 */ }"></div>
四、ref() vs reactive() 对比
4.1 核心差异对比
特性 | ref() | reactive() |
---|---|---|
包装对象 | RefImpl 对象 | Proxy 对象 |
值访问 | 需要 .value |
直接访问 |
原始值支持 | ✅ | ❌ |
深层响应 | ✅ | ✅ |
类型保留 | 完整保留 | 部分特殊处理 |
解构响应性 | 支持解构属性 | 需要toRefs |
整体替换 | ✅ | ❌ (会破坏响应性) |
4.2 使用场景指南
场景 | 推荐 | 说明 |
---|---|---|
基本类型值 | ref | 字符串、数字等 |
复杂对象 | reactive | 表单对象、状态对象 |
组件状态 | reactive | 逻辑相关的状态组 |
模板引用 | ref | DOM元素/组件实例 |
函数参数 | ref | 避免响应式对象解构问题 |
组合函数返回值 | ref | 更灵活的响应式值 |
4.3 性能考量
- 创建开销:reactive() 的 Proxy 创建比 ref() 稍重
- 内存占用:ref() 有额外包装对象开销
- 更新效率:两者更新效率相当
- 最佳实践:
// 大型对象使用reactive更高效 const bigData = reactive(largeDataset) // 独立值使用ref const loading = ref(false)
五、响应式系统高级用法
5.1 响应式转换工具
import { toRef, toRefs, isRef, isReactive, isProxy } from 'vue'
const state = reactive({ count: 0 })
// 对象属性转为ref
const countRef = toRef(state, 'count')
// 整个对象转为ref集合
const refs = toRefs(state) // { count: Ref<number> }
// 类型检查
console.log(isRef(countRef)) // true
console.log(isReactive(state)) // true
console.log(isProxy(state)) // true
5.2 浅层响应式
import { shallowReactive, shallowRef } from 'vue'
// 浅层reactive:只响应根级属性
const shallowState = shallowReactive({
nested: { value: 1 } // 内部非响应
})
// 浅层ref:不自动解包内部值
const shallowObj = shallowRef({ count: 0 })
shallowObj.value.count++ // 不会触发更新
5.3 自定义Ref
import { customRef } from 'vue'
function useDebouncedRef(value, delay = 200) {
let timeout
return customRef((track, trigger) => {
return {
get() {
track()
return value
},
set(newValue) {
clearTimeout(timeout)
timeout = setTimeout(() => {
value = newValue
trigger()
}, delay)
}
}
})
}
// 使用
const text = useDebouncedRef('', 500)
5.4 响应式工具函数
import { markRaw, readonly, watchEffect } from 'vue'
// 1. 标记非响应式对象
const nonReactiveObj = markRaw({ shouldNotBeTracked: true })
// 2. 创建只读响应式对象
const readOnlyState = readonly(state)
// 3. 响应式副作用
watchEffect(() => {
console.log(`Count: ${state.count}`) // 自动追踪依赖
})
六、响应式原理深度剖析
6.1 依赖收集与触发
6.2 Proxy 拦截器实现
const reactiveHandlers = {
get(target, key, receiver) {
track(target, key) // 收集依赖
const res = Reflect.get(target, key, receiver)
if (isObject(res)) {
return reactive(res) // 递归响应式
}
return res
},
set(target, key, value, receiver) {
const oldValue = target[key]
const result = Reflect.set(target, key, value, receiver)
if (hasChanged(value, oldValue)) {
trigger(target, key) // 触发更新
}
return result
}
// 省略deleteProperty/has等其他拦截
}
6.3 响应式系统三大核心
Effect(副作用):
let activeEffect class ReactiveEffect { run() { activeEffect = this this.fn() // 执行过程中收集依赖 } }
Dep(依赖集合):
class Dep { constructor() { this.subscribers = new Set() } depend() { if (activeEffect) this.subscribers.add(activeEffect) } notify() { this.subscribers.forEach(effect => effect.run()) } }
TargetMap(目标映射):
const targetMap = new WeakMap() function track(target, key) { if (!activeEffect) return let depsMap = targetMap.get(target) if (!depsMap) targetMap.set(target, (depsMap = new Map())) let dep = depsMap.get(key) if (!dep) depsMap.set(key, (dep = new Dep())) dep.depend() }
七、最佳实践与性能优化
7.1 选择指南
场景 | 推荐 | 替代方案 | 原因 |
---|---|---|---|
独立原始值 | ref | reactive | 更简洁 |
相关状态组 | reactive | 多个ref | 逻辑聚合 |
组件props | ref | - | 标准做法 |
全局状态 | reactive | ref | 结构化数据 |
需要解构 | ref + toRefs | reactive | 保持响应性 |
7.2 性能优化策略
避免大型响应式对象:
// 避免 const hugeObj = reactive(/* 大型数据集 */) // 推荐:使用shallowRef或手动管理 const data = ref({}) data.value = largeDataset // 替换而非响应式
减少不必要的响应式:
// 静态配置无需响应式 const config = markRaw({ itemsPerPage: 10, maxRetries: 3 })
合理使用计算属性:
const sortedList = computed(() => [...list.value].sort((a, b) => a.id - b.id) )
批量更新优化:
import { nextTick } from 'vue' const updateMultiple = () => { state.a = 1 state.b = 2 nextTick(() => { // DOM更新后执行 }) }
7.3 响应式数据模式
// 组合式函数模式
export function useCounter(initial = 0) {
const count = ref(initial)
const increment = () => count.value++
const decrement = () => count.value--
return {
count,
increment,
decrement
}
}
// 在组件中使用
const { count, increment } = useCounter()
八、常见问题与解决方案
8.1 响应性丢失问题
问题:解构导致响应性丢失
const state = reactive({ count: 0 })
const { count } = state // 失去响应性
解决方案:
// 1. 使用toRefs
const { count } = toRefs(state)
// 2. 直接访问原对象
state.count
// 3. 使用computed
const count = computed(() => state.count)
8.2 循环引用问题
问题:对象循环引用导致无限递归
const obj = reactive({})
obj.self = obj // 循环引用
解决方案:
// 1. 避免循环引用
// 2. 使用markRaw切断响应链
obj.self = markRaw(obj)
8.3 数组操作问题
问题:直接索引修改数组不触发更新
const list = reactive([1, 2, 3])
list[0] = 9 // 不会触发更新
解决方案:
// 1. 使用变异方法
list.splice(0, 1, 9)
// 2. 整个替换
list.value = [9, 2, 3]
// 3. 使用Vue.set (Vue 3中为set)
import { set } from 'vue'
set(list, 0, 9)