Ref与Reactive的区别
ref与reactive的区别已经是我们面试中的老问题了,一般情况下我们都会通过对象的类型来决定:基本类型使用ref
,引用类型使用reactive
。但其实ref
也能用来创建引用类型的对象,即Object
和Array
,那么我们应该在什么情况下使用ref
,什么情况下使用reactive
呢,接下来就从源码入手,分析一下这两个的区别。
我们都知道,Vue3的双向绑定的实现逻辑已经从defineProperty
变成了Proxy
,ref
与reactive
都会创建一个Proxy
对象,接下来就来看看是如何实现的
ref 与 shallowRef
ref
与shallowRef
的区别在于是否为深响应式,一般情况下我们都是使用ref
,而shallowRef
只会对.value
的变更进行响应式处理,即使是引用类型也是如此。使用场景可以参考减少大型不可变数据的响应性开销
function ref(value) {
return createRef(value, false);
}
function shallowRef(value) {
return createRef(value, true);
}
function createRef(rawValue, shallow) {
// 如果已经是ref对象,则直接返回,不做任何事情
if (isRef(rawValue)) {
return rawValue;
}
return new RefImpl(rawValue, shallow);
}
以上是ref
与shallowRef
方法的实现,我们可以看到两个方法都是返回了createRef
方法,只不过 第二个参数(shallow) 的传参不一样,而createRef
方法也是十分的简单:先判断了当前传入的对象是否已经是Ref
对象,若是则直接返回,否则返回一个RefImpl
对象,接下来看看isRef
和RefImpl
的具体实现:
function isRef(r) {
return !!(r && r.__v_isRef === true);
}
var RefImpl = class {
constructor(value, __v_isShallow) {
// 是否浅ref
this.__v_isShallow = __v_isShallow;
this.dep = void 0;
// 是否为Ref对象
this.__v_isRef = true;
this._rawValue = __v_isShallow ? value : toRaw(value);
// 将值赋值给 _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;
// 如果新赋予的值是一个对象,那么包装成Proxy
this._value = useDirectValue ? newVal : toReactive(newVal);
triggerRefValue(this, 4 /* Dirty */, newVal);
}
}
}
// toReactive首先判断value是否为对象,若是对象则进行包装,否则直接返回
var toReactive = (value) => isObject(value) ? reactive(value) : value;
我们可以看到在新建RefImpl
类的时候通过this.__v_isRef = true
来定义是否为 Ref 对象,isRef
方法也是基于这个变量来返回。
在RefImpl
类中,可以知道值是存在_value
中的,而get value
也是返回了_value
的值,因此我们在对Ref
对象进行操作的时候需要通过.value
的方式进行修改,而不能直接进行赋值。
而_value
的定义我们可以发现,当不是shallow
的时候,将value
通过toReactive
方法转变成了reactive
。由此知晓:ref 的 .value 与 reactive 其实是同一个东西,接下来就来看看 reactive 到底是个啥
reactive 与 shallowReactive
reactive
与 shallowReactive
的区别同 ref
function reactive(target) {
if (isReadonly(target)) {
return target;
}
return createReactiveObject(
target,
false,
mutableHandlers,
mutableCollectionHandlers,
reactiveMap
);
}
function shallowReactive(target) {
return createReactiveObject(
target,
false,
shallowReactiveHandlers,
shallowCollectionHandlers,
shallowReactiveMap
);
}
我们可以发现跟ref
一样,他们都返回了一个createReactiveObject
function createReactiveObject(target, isReadonly2, baseHandlers, collectionHandlers, proxyMap) {
// 判断是否为对象,若不是则抛出异常
if (!isObject(target)) {
if (true) {
warn(`value cannot be made reactive: ${String(target)}`);
}
return target;
}
if (target["__v_raw" /* RAW */] && !(isReadonly2 && target["__v_isReactive" /* IS_REACTIVE */])) {
return target;
}
const existingProxy = proxyMap.get(target);
if (existingProxy) {
return existingProxy;
}
const targetType = getTargetType(target);
if (targetType === 0 /* INVALID */) {
return target;
}
const proxy = new Proxy(
target,
targetType === 2 /* COLLECTION */ ? collectionHandlers : baseHandlers
);
proxyMap.set(target, proxy);
return proxy;
}
到了这一步就已经得知,ref
和reactive
返回了Proxy的原理。
总结
基本类型的数据使用ref
,引用类型根据赋值方式来决定使用ref
还是reactive
如果是要直接替换整个对象,也就是赋值的方式,就使用ref
如果是要修改对象的属性,就使用reactive
因为ref.value
替换对象仍能保持响应式,而reactive
不行
ref
- ref的变量必须使用
.value
赋值,不然会把ref变成普通的数据,失去响应式。 - ref的值如果是对象,里面的对象是响应式的,因为引用类型会先用
Proxy
包装再赋值。 - 如果是
shallowRef
则对象不会用Proxy
包装。 ref(reactive({a:123}))
等价于ref({a:123})
,若在shallowRef
中使用reactive
,shallowRef
会失去作用
reactive
- 无需
.value
即可修改对象的值且具备响应式