我们虽然走的很慢,但从未停止过脚步。
我们经常会使用到 ref 和 reactive,那么他们在底层的实现上有什么区别和联系呢?在说这个问题之前,让我们先看一下 ref 和 reactive 的对比。
ref 和 reactive 对比
- ref 可以定义基本数据类型和引用数据类型,reactive 只能定义引动数据类型。
- 在 script 标签中,ref 取值需要通过 .value 的形式。
- 对 reactive 重新赋值会导致其失去响应性。并且传给一个函数参数时也会失去响应性。
注意:对 ref 和 reactive 进行解构都会导致其失去响应式
由于 ref 源码中用到了 reactive 函数,让我们先看一下 reactive 的源码。
reactive 源码
目录:core-main/packages/reactivity/reactive.ts
先看一下 reactive 函数
export function reactive(target: object) {
// if trying to observe a readonly proxy, return the readonly version.
if (isReadonly(target)) {
return target
}
return createReactiveObject(
target,
false,
mutableHandlers,
mutableCollectionHandlers,
reactiveMap,
)
}
在该函数中主要调用了 createReactiveObject 方法:
function createReactiveObject(
target: Target,
isReadonly: boolean,
baseHandlers: ProxyHandler<any>,
collectionHandlers: ProxyHandler<any>,
proxyMap: WeakMap<Target, any>,
) {
......
const proxy = new Proxy(
target,
targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers,
)
proxyMap.set(target, proxy)
return proxy
}
而在 createReactiveObject 函数中主要使用了 ES6 新增的 Proxy 来实现响应式,其使用方法如下:
const proxy = new Proxy(target, {
set(target, prop, receiver) {
},
get(target, prop, value, receiver) {
},
......
})
Proxy 为代理模式,第一个参数是所要代理的对象,第二个参数是一个对象,可以在这个对象里写 set, get 函数监听对象的属性操作。所以在 createReactiveObject 函数中主要就是返回了一个 Proxy 的代理对象,而 collectionHandlers 和 baseHandlers 就是对所要代理的对象的配置对象。而在这个对象中在 set 时去 trigger 去收集依赖,而在 get 时 track 去触发依赖的更新。这就是 reactive 的一个基本实现过程。
需要注意的是,在进行依赖的收集时,使用的时 WeakMap,它和 Map 的区别为 Map 是强引用而 WeakMap 为弱引用,当数据不在被使用的时候,WeakMap 会对其进行清除,从而防止内存泄漏。
ref 源码
目录:core-main/packages/reactivity/ref.ts
ref函数:
export function ref(value?: unknown) {
return createRef(value, false)
}
在 ref 函数中调用了 createRef ,那,让我们看看 createRef 函数。
function createRef(rawValue: unknown, shallow: boolean) {
if (isRef(rawValue)) {
return rawValue
}
return new RefImpl(rawValue, shallow)
}
在 createRef 函数中,先进行了一次判断,如果所传入的是 ref 对象,那么直接返回,如果不是,则返回一个 RefImpl 类。RefImpl 类的具体实现如下:
class RefImpl<T> {
private _value: T
private _rawValue: T
public dep?: Dep = undefined
public readonly __v_isRef = true
constructor(
value: T,
public readonly __v_isShallow: boolean,
) {
this._rawValue = __v_isShallow ? value : toRaw(value)
this._value = __v_isShallow ? value : toReactive(value)
}
get value() {
trackRefValue(this)
return this._value
}
set value(newVal) {
const useDirectValue =
this.__v_isShallow || isShallow(newVal) || isReadonly(newVal)
newVal = useDirectValue ? newVal : toRaw(newVal)
if (hasChanged(newVal, this._rawValue)) {
this._rawValue = newVal
this._value = useDirectValue ? newVal : toReactive(newVal)
triggerRefValue(this, DirtyLevels.Dirty, newVal)
}
}
}
让我们看一下构造函数中的 this._value = __v_isShallow ? value : toReactive(value) 代码,如果 __v_isShallow 为 fasle 则会执行 toReactive 函数(__v_isShallow主要是实现浅层响应式的 –> shallowRef), toReactive 函数如下:
export const toReactive = <T extends unknown>(value: T): T
=> isObject(value) ? reactive(value) : value
在该函数中使用 isObject 进行判断,如果它是一个对象,则直接调用 reactive 函数,否则原值返回进行进一步的操作。
所以在 ref 的底层当遇到对象时,也会直接调用 reactive 函数,那么在定义对象时使用 reactive 的性能肯定是比 ref 高的。
结语
那为什么使用 reactive 赋值会失去响应性呢?这是因为当用一个对象直接对其赋值时,它并没有使用 Proxy代理 对所赋值的对象进行依赖的收集和更新,自然也就失去了响应性。其他使用 reactive 导致失去相应性的场景也是如此。要想使其获得响应性,可以使用 toRef 和 toRefs(主要应用场景为解构赋值)。
ref 必须使用 .value 获取值的原因:对于简单类型而言,无法使用 Proxy(传入的第一个参数为对象),所以 Vue 对其封装为一个具有 value 属性对象,便于对其进行依赖的收集和更新。
原文链接:https://juejin.cn/post/7350880189836820516 作者:送你颗糖呀