在Vue 3中,Ref和Reactive是新的响应式API。它们是在Vue 2中的数据绑定的基础上进一步发展的,提供了更好的性能和更好的开发体验。
Ref
Ref是一个用于创建具有响应性的简单值的函数。它将任何类型的值转换为具有.value属性的响应式对象,该属性可以读取和修改该值。
使用方式
创建Ref对象
import { ref } from 'vue'
const name = ref('Allen')
访问属性
// 要访问这个值, 可以使用.value属性
console.log(name.value) // Allen
修改属性
name.value = "Alan"
console.log(name.value) // Alan
源码解析
Ref的实现方式很简单,它实际上只是一个包含value属性的JavaScript对象。当Ref对象的value属性被读取或设置时,它将触发getter或setter函数。这是通过使用Proxy对象来实现的。在Ref函数中,Vue3使用了以下代码来创建一个Ref对象:
function ref(value) {
return createRef(value)
}
function createRef(rawValue, shallow = false) {
// 使用一个对象来包装原始值,这个对象有一个 value 属性
const r = {
_isRef: true,
value: shallow ? rawValue : convert(rawValue)
}
// 然后创建一个代理对象,用于观察 value 属性的访问和修改
return new Proxy(r, {
get(target, key, receiver) {
// 如果访问 value 属性,则返回它的值
if (key === 'value') {
trackRefValue(target)
return target._value
}
// 其他情况,直接从对象本身获取属性
return Reflect.get(target, key, receiver)
},
set(target, key, value, receiver) {
// 如果修改 value 属性,则更新它的值并通知更新
if (key === 'value') {
if (value !== target._value) {
target._value = shallow ? value : convert(value)
triggerRefValue(target)
}
return true
}
// 其他情况,直接从对象本身设置属性
return Reflect.set(target, key, value, receiver)
}
})
}
Reactive
Reactive是一个用于创建具有响应性的对象的函数。它将对象转换为一个代理对象,该代理对象具有与原始对象相同的属性和方法,但是这些属性和方法都具有响应性。
使用方式
创建Reactive对象
import { reactive } from 'vue'
const person = reactive({
name: 'Allen',
age: 28
})
访问Reactive对象
console.log(person.name) // Allen
修改属性
person.age = 30
console.log(person.age) // 30
源码解析
实现方式与Ref对象类似。它们都使用Proxy对象来实现响应式。但是,Reactive对象比Ref对象更复杂,因为它需要处理嵌套的对象。
function reactive(target) {
// 如果 target 已经是响应式对象,则直接返回它
if (target && target._isReactive) {
return target
}
// 如果 target 已经被包装,则使用原始对象
const existingProxy = reactiveMap.get(target)
if (existingProxy) {
return existingProxy
}
// 创建一个新的响应式代理对象
const proxy = new Proxy(target, {
get(target, key, receiver) {
const res = Reflect.get(target, key, receiver)
track(target, TrackOpTypes.GET, key)
return isObject(res) ? reactive(res) : res
},
set(target, key, value, receiver) {
const oldValue = target[key]
// 首先更新值
const result = Reflect.set(target, key, value, receiver)
// 如果新值和旧值相同,则不需要触发更新
if (result && oldValue !== value) {
trigger(target, TriggerOpTypes.SET, key, value, oldValue)
}
return result
},
deleteProperty(target, key) {
const hasKey = hasOwn(target, key)
const oldValue = target[key]
const result = Reflect.deleteProperty(target, key)
if (result && hasKey) {
trigger(target, TriggerOpTypes.DELETE, key, undefined, oldValue)
}
return result
}
})
// 把包装后的对象标记为已经响应式,并存储到 reactiveMap 中
markRaw(target)
reactiveMap.set(target, proxy)
return proxy
}
总结
- Ref用于包装单个值,而Reactive用于包装对象。
- Ref返回一个包含
.value属性的对象,而Reactive返回一个代理对象,该代理对象具有与原始对象相同的属性和方法,但是这些属性和方法都具有响应性。 - 在使用Ref时,您需要使用
.value属性访问值或修改值。而在Reactive中,您可以像操作普通JavaScript对象一样访问和修改对象的属性。 - Ref可以更轻松地与基于模板的Vue组件一起使用,而Reactive则更适用于基于函数的组件或独立的JavaScript代码。
defineProperty API 的不足
- 不能监听新增或删除属性:
Object.defineProperty只能拦截已经存在的属性的读取和设置操作,不能监听新增或删除属性的操作。在实际使用时可通过Vue.set方式操作属性。 - 只能监听对象属性的变化,不能监听数组的变化:虽然可以使用
Object.defineProperty监听对象属性的变化,但是它无法直接监听数组的变化。在Vue2.x中,需要使用hack的方法来监听数组的变化。可以通过重写数组的push、shift等方法触发更新 - 性能问题:由于
Object.defineProperty只能逐个属性地监听对象的变化,所以对于大型对象来说,会导致性能问题。Vue2引入了watcher机制。watcher会记录哪些组件依赖于哪些数据,只有当数据发生变化时才会更新这些组件。 - 复杂性:使用
Object.defineProperty实现响应式需要写很多代码,并且需要非常深入地了解JavaScript语言的特性,使得代码难以维护和理解。
为什么要用 Proxy API 替代 defineProperty API?
- 更好的性能表现:使用Proxy API实现响应式,相比于
Object.defineProperty实现响应式,有更好的性能表现。这是因为Proxy可以直接代理整个对象,而Object.defineProperty只能一个属性一个属性地进行代理,因此在处理大型对象时Proxy更为高效。 - 更好的语法支持:使用Proxy API可以提供更自然的语法支持,使得开发者在使用响应式API时可以使用与普通JavaScript代码更为相似的语法。相比之下,使用
Object.defineProperty需要更多的代码,并且需要更深入地了解JavaScript语言特性。 - 更丰富的代理能力:Proxy API支持拦截的操作更多,包括读取、设置、删除、枚举等等,而
Object.defineProperty只能拦截读取和设置操作。 - 更好的错误提示:使用Proxy API在出错时可以提供更好的错误提示,帮助开发者更快地发现和解决问题。