聊聊vue3中ref和reactive的区别与底层实现
Vue 3 中引入了新的响应式 API,包括 ref
和 reactive
,这两个API都用于创建响应式引用,但是它们之间存在一些关键的区别,以及它们在底层的实现机制也有所不同。
一、ref
和 reactive
的区别
0. 一句话区分
ref 用来定义: 基本数据类型的数据 。
reactive 用来定义: 对象(或数组)类型的数据 。
1. 使用方式
-
ref
:ref
创建的是一个带有.value
属性的对象,用于封装基本类型(如字符串、数字等)或者复杂类型的响应式引用。- 当你使用
ref
时,必须通过.value
访问或修改值。 - 适用于单值的状态管理。
-
reactive
:reactive
直接返回一个对象,这个对象是原始对象的响应式代理。- 你可以像操作普通对象一样操作
reactive
创建的对象,不需要额外的属性访问器。 - 更适合用于对象和数组的状态管理,因为它不需要额外的包装层。
在下面的示例中,单击按钮后 personRef
和 personReactive
都会修改 name
属性,随后便会在 UI 上得到响应,但对普通 JS 对象 person
来说就不会。
<template>
{{ person.name }} <!-- 不会变为 张三-->
{{ personRef.name }} <!-- 会变为 张三-->
{{ personReactive.name }} <!-- 会变为 张三-->
<button @click="changeName('张三')">Change Name</button>
</template>
<script setup lang="ts">
import { ref, reactive } from 'vue'
const person = { name: 'John' }
const personRef = ref({ name: 'John' })
const personReactive = reactive({ name: 'John' })
const changeName = (name: string) => {
person.name = name
personRef.value.name = name
personReactive.name = name
}
</script>
2.响应性范围
-
ref
:- 只有
.value
内部的值是响应式的。
- 只有
-
reactive
:- 对象或数组中的所有属性都是响应式的。
二、底层实现
1、Ref 的VUE实现
下面是 Vue.js 3 中 ref() 实现的部分代码。
ref
是通过一个特殊的 RefImpl
类(或类似的实现)来创建的,这个类负责包装原始值,并且通过 Proxy
或其他机制实现对值的响应式追踪。 RefImpl
类实现了 .value
的 getter 和 setter 方法,通过调用 track
和 trigger
来处理依赖的收集和触发。而 convertToReactive
函数可能是调用 reactive
或者使用 Proxy
来创建响应式数据结构的函数。
ref在面对对象数据类型时,仍调用了toReactive函数,把ref转化为了reactive。
//这是一段简化后的VUE实现
function ref(value) {
return new RefImpl(value);
}
class RefImpl {
constructor(value) {
this._rawValue = value; // 原始值
this._value = convertToReactive(value); // 转换成响应式的
this.dep = new Dep(); // 依赖收集器
}
get value() {
track(this.dep); // 收集依赖
return this._value;
}
set value(newVal) {
this._rawValue = newVal;
this._value = convertToReactive(newVal); // 将新值转换为响应式
trigger(this.dep); // 触发依赖更新
}
}
// 假设我们有 convertToReactive 和依赖收集/触发的函数
function convertToReactive(value) {
// 这里可以是 reactive 或者是更复杂的 Proxy 创建逻辑
return reactive(value);
}
function track(dep) {
// 收集依赖的逻辑
}
function trigger(dep) {
// 触发依赖更新的逻辑
}
2、Reactive的VUE实现
当我们调用 reactive 函数时,会将传入的普通 JavaScript 对象转换为一个 Proxy 对象,通过拦截 get 和 set 操作,实现对整个对象的监听。当读取对象的属性时,会触发 get 操作,返回对应属性的值。当修改对象的属性时,会触发 set 操作,将新的值赋给对应的属性。
核心是利用了 JavaScript 的 Proxy
对象来实现响应式机制,通过定义不同的处理器(mutableHandlers
, mutableCollectionHandlers
)来捕捉对象属性的读写操作,并通过 WeakMap 缓存已创建的代理对象以提高性能。这种设计允许 Vue 3 能够有效地追踪和响应数据变化,从而实现视图的自动更新。
//从vue源码中截取.
export function reactive<T extends object>(target: T): Reactive<T>
export function reactive(target: object) {
// if trying to observe a readonly proxy, return the readonly version.
if (isReadonly(target)) {
return target
}
//这里调用了 createReactiveObject 函数来创建响应式对象。传入的参数包括目标对象、一个布尔值表示是否只读、基础的 Proxy 处理器、集合类型的处理器以及一个 WeakMap 用来缓存已创建的代理对象。
return createReactiveObject(
target,
false,
mutableHandlers,
mutableCollectionHandlers,
reactiveMap,
)
}
/*
此函数的主要作用:
检查目标对象是否为对象类型,如果不是则警告并直接返回。
检查目标对象是否已经是一个代理,如果是并且不是从 reactive 到 readonly 的转换,那么直接返回现有的代理。
检查代理映射表中是否已经有目标对象的代理,如果有则返回已存在的代理,避免重复创建。
检查目标对象是否是可以被观察的类型,如果不可观察直接返回。
根据目标对象的类型选择适当的 Proxy 处理器,对于集合类型(如 Set、Map)使用特定的处理器。
使用 new Proxy 创建一个新的代理对象,其中包含了目标对象和对应的处理器。
将新创建的代理对象添加到代理映射表中,以便后续查找。
最终返回新创建的代理对象。
*/
function createReactiveObject(
target: Target,
isReadonly: boolean,
baseHandlers: ProxyHandler<any>,
collectionHandlers: ProxyHandler<any>,
proxyMap: WeakMap<Target, any>,
) {
if (!isObject(target)) {
if (__DEV__) {
warn(
`value cannot be made ${isReadonly ? 'readonly' : 'reactive'}: ${String(
target,
)}`,
)
}
return target
}
// target is already a Proxy, return it.
// exception: calling readonly() on a reactive object
if (
target[ReactiveFlags.RAW] &&
!(isReadonly && target[ReactiveFlags.IS_REACTIVE])
) {
return target
}
// target already has corresponding Proxy
const existingProxy = proxyMap.get(target)
if (existingProxy) {
return existingProxy
}
// only specific value types can be observed.
const targetType = getTargetType(target)
if (targetType === TargetType.INVALID) {
return target
}
const proxy = new Proxy(
target,
targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers,
)
proxyMap.set(target, proxy)
return proxy
}