提出问题
先上测试源码:
<template>
<h1>{{ foo.a }}</h1>
<h1>{{ bar.a }}</h1>
<button @click="handleClick">点我</button>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue'
let foo = ref({ a: 1, b: 2, c: 3 })
let bar = reactive({ a: 4, b: 5, c: 6 })
const handleClick = () => {
foo.value = {
a: 11
}
bar = {
a: 99
}
console.log('handleClick-->foo', foo)
console.log('handleClick-->bar', bar)
}
onMounted(() => {
console.log('onMounted-->foo', foo)
console.log('onMounted-->bar', bar)
})
</script>
点击后的输出结果:
ref 定义的对象,重新赋值后没有失去响应式,但是 reactive 定义的对象,重新赋值后失去了响应式,变成了普通对象。
我们在官网可以看到:
官网描述,使用 ref 定义对象时,内部引用了 reactive 函数处理深层次的响应式对象
那么问题来了:为什么 ref 调用 reactive 处理对象,为什么重新赋值后,没有失去响应式,但是 reactive 却失去了响应式?
过程
我们去看看源码咋写的:
class RefImpl {
constructor(value, __v_isShallow) {
this.__v_isShallow = __v_isShallow;
this.dep = undefined;
this.__v_isRef = true;
this._rawValue = __v_isShallow ? value : toRaw(value);
this._value = __v_isShallow ? value : toReactive(value);
}
get value() {
trackRefValue(this);
return this._value; // get方法返回的是_value的值
}
set value(newVal) {
newVal = this.__v_isShallow ? newVal : toRaw(newVal);
if (hasChanged(newVal, this._rawValue)) {
this._rawValue = newVal;
this._value = this.__v_isShallow ? newVal : toReactive(newVal); // set方法调用 toReactive 方法
triggerRefValue(this, newVal);
}
}
}
- 我们读取 xxx.value 值的时候,getter 返回的是 xxx._value 的值,就是说,ref 定义的数据,value 和 _value 的值是一样的
我们修改 xxx.value 值的时候,setter 调用 toReactive 方法
const toReactive = (value) => isObject(value) ? reactive(value) : value;
- toReactive 方法判断是否是对象,是的话就调用 reactive 方法(印证了官网说的,ref 定义对象时,底层调用 reactive 方法实现)
function reactive(target) {
// if trying to observe a readonly proxy, return the readonly version.
if (isReadonly(target)) {
return target;
}
return createReactiveObject(target, false, mutableHandlers, mutableCollectionHandlers, reactiveMap);
}
reactive 方法,先判断数据是否是 “只读” 的,不是就返回 createReactiveObject 方法处理后的数据
createReactiveObject 方法将对象通过 proxy 处理为响应式数据
结论
ref 定义数据(包括对象)时,都会变成 RefImpl(Ref 引用对象) 类的实例,无论是修改还是重新赋值都会调用 setter,都会经过 reactive 方法处理为响应式对象。
但是 reactive 定义数据(必须是对象),是直接调用 reactive 方法处理成响应式对象。如果重新赋值,就会丢失原来响应式对象的引用地址,变成一个新的引用地址,这个新的引用地址指向的对象是没有经过 reactive 方法处理的,所以是一个普通对象,而不是响应式对象
记在最后:
想到这个问题后,我就开始在网上搜索了好久,但是相关文章很少,有关的文章也没看的太明白,所以最后决定去看源码
源码地址是 node_modules 下的 reactive 下的 dist 文件夹,里面有多个 js 文件,并且涉及到 ref 和 reactive 原理的方法基本一致,于是我用了最笨的方法,每个涉及的文件都做了debugger,最后发现是调用了 reactivity.esm-browser.js 文件的方法,然后不断的 debugger 看完了大致的流程。
当我们遇到问题并且网上提供的帮助少之又少的情况下,查看源码是我们解决问题的快捷方法。 小小记录一下,与大家共勉。
PS: 我使用的是 webstorm 编辑器,可以通过 ctrl+点击方法名 跳转到方法创建处,使用 vscode 的话,需要安装插件才有这个功能。