Reactive与Ref原理
Reactive与Ref原理
Vue.js的响应式系统是其核心特性之一,它允许开发者声明式地描述UI与数据之间的关系。reactive
和ref
是Vue3中创建响应式数据的两种主要方式,虽然它们最终都实现了数据的响应式追踪,但在底层实现和使用场景上存在明显差异。
reactive的基本原理
reactive
函数接收一个普通对象,返回该对象的Proxy代理。Proxy是ES6引入的特性,允许拦截对目标对象的底层操作。Vue3通过Proxy的get和set陷阱实现依赖收集和触发更新。
const obj = reactive({ count: 0 })
// 底层Proxy实现示意
function reactive(target) {
return new Proxy(target, {
get(target, key, receiver) {
track(target, key) // 依赖收集
return Reflect.get(target, key, receiver)
},
set(target, key, value, receiver) {
const result = Reflect.set(target, key, value, receiver)
trigger(target, key) // 触发更新
return result
}
})
}
当访问obj.count
时,Proxy的get陷阱会执行,Vue会记录当前正在运行的effect(副作用函数)。当修改obj.count
时,set陷阱触发,Vue会找到所有依赖这个属性的effect并重新执行它们。
ref的基本原理
ref
主要用于包装基本类型值(如number、string等),因为Proxy无法直接代理基本类型。ref通过创建一个包含value属性的响应式对象来实现:
const count = ref(0)
// 简化实现
function ref(value) {
const refObject = {
get value() {
track(refObject, 'value')
return value
},
set value(newVal) {
value = newVal
trigger(refObject, 'value')
}
}
return refObject
}
在模板中使用ref时,Vue会自动解包,不需要写.value
。但在JavaScript中必须通过.value
访问:
// 模板中
<template>
<div>{{ count }}</div> <!-- 自动解包 -->
</template>
// JS中
console.log(count.value) // 需要.value
两者差异比较
-
数据类型处理:
reactive
只接受对象类型ref
可以接受任何类型,包括基本类型和对象
-
访问方式:
reactive
对象直接访问属性ref
需要通过.value
访问
-
替换整个对象:
reactive
直接替换会失去响应性ref
可以整体替换,因为响应性绑定在ref对象本身
// reactive限制
const state = reactive({ count: 0 })
state = { count: 1 } // 失去响应性
// ref灵活性
const stateRef = ref({ count: 0 })
stateRef.value = { count: 1 } // 保持响应性
响应式系统的工作流程
-
依赖收集阶段: 当组件渲染时,会执行render函数,访问响应式数据时会触发getter,将当前正在执行的effect(渲染函数)注册为依赖。
-
触发更新阶段: 当响应式数据变化时,setter会触发,通知所有依赖的effect重新执行,实现UI更新。
// 简化的effect实现
let activeEffect
function effect(fn) {
activeEffect = fn
fn() // 执行过程中会触发getter,收集依赖
activeEffect = null
}
// 依赖收集
const depsMap = new WeakMap()
function track(target, key) {
if (!activeEffect) return
let dep = depsMap.get(target)
if (!dep) {
dep = new Map()
depsMap.set(target, dep)
}
let effects = dep.get(key)
if (!effects) {
effects = new Set()
dep.set(key, effects)
}
effects.add(activeEffect)
}
// 触发更新
function trigger(target, key) {
const dep = depsMap.get(target)
if (!dep) return
const effects = dep.get(key)
effects && effects.forEach(effect => effect())
}
实际应用场景
- reactive适用场景:
- 复杂对象结构
- 需要解构使用的场景(配合toRefs)
- 表单对象等嵌套数据结构
const form = reactive({
user: {
name: '',
age: 0
},
preferences: {
theme: 'light'
}
})
// 解构保持响应性
const { user, preferences } = toRefs(form)
- ref适用场景:
- 基本类型值
- 需要重新赋值的响应式变量
- 模板ref引用DOM元素
- 组合式函数返回值
// DOM引用
const inputRef = ref(null)
// 组合式函数
function useCounter() {
const count = ref(0)
const increment = () => count.value++
return { count, increment }
}
性能考量
-
内存开销:
reactive
为整个对象创建Proxyref
为每个值创建包装对象
-
访问速度:
reactive
直接访问属性略快ref
需要多一层.value
访问
-
批量更新: Vue会异步批量处理更新,无论使用哪种方式,多次修改最终只会触发一次重新渲染。
响应式API进阶
- shallowRef:
只对
.value
的引用变化响应,不深度追踪内部值。
const shallow = shallowRef({ count: 0 })
shallow.value.count++ // 不会触发更新
shallow.value = { count: 1 } // 会触发更新
- readonly: 创建只读的响应式代理,任何修改尝试都会失败。
const ro = readonly({ count: 0 })
ro.count++ // 警告并失败
- customRef: 创建自定义的ref,可以控制依赖追踪和触发更新的时机。
function debouncedRef(value, delay = 200) {
let timeout
return customRef((track, trigger) => {
return {
get() {
track()
return value
},
set(newValue) {
clearTimeout(timeout)
timeout = setTimeout(() => {
value = newValue
trigger()
}, delay)
}
}
})
}
与Vue2的对比
-
Object.defineProperty的局限:
- 无法检测对象属性的添加/删除
- 对数组的变异方法需要特殊处理
- 需要递归遍历对象的所有属性
-
Proxy的优势:
- 可以拦截更多操作(如in、delete等)
- 性能更好,按需响应
- 支持Map、Set等新集合类型
// Vue2的响应式实现示意
function defineReactive(obj, key) {
let value = obj[key]
const dep = new Dep()
Object.defineProperty(obj, key, {
get() {
dep.depend() // 收集依赖
return value
},
set(newVal) {
value = newVal
dep.notify() // 触发更新
}
})
}
常见问题与解决方案
- 响应式丢失:
解构reactive对象会导致响应式丢失,需要使用
toRefs
。
const state = reactive({ count: 0 })
// 错误方式 - 响应式丢失
let { count } = state
// 正确方式
let { count } = toRefs(state)
- 循环引用: reactive处理循环引用时会自动处理,但要注意无限递归的情况。
const obj = reactive({})
obj.self = obj // 允许但不推荐
- 原始值包装: 直接解包ref到reactive对象中会保持响应性连接。
const count = ref(0)
const state = reactive({ count })
count.value++ // state.count也会更新
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn
上一篇:组件状态共享模式比较
下一篇:Proxy实现响应式