01/ 基础类型的响应性 —— ref
在vue3里面,我们可以通过 reactive 来实现引用类型的响应性,那么基础类型的响应性如何来实现呢?
可能你会想到这样来实现:
const count = reactive({value: 0}) count.value += 1
这么做确实可以实现,而且也很像 ref 的使用方式,都是要 .value 嘛。那么 ref内部 是不是这么实现的呢?
我们先定义两个 ref 的实例并且打印看看。
const refCount = ref(0) // 基础类型 console.log('refCount ', refCount ) const refObject = ref({ value: 0 }) // 引用类型 console.log('refObject ', refObject )
看一下结果:
基础类型的 ref
引用类型的 ref
我们都知道 reactive 是通过 ES6 的 Proxy 来实现的,基础类型的 ref 显然和 Proxy 没啥关系,而引用类型的 ref 是先把原型变成 reactive, 然后再挂到 value 上面。这样看来,和我们的猜测不太一样呢,那么 ref 到底是如何实现的呢?我们可以看一下 ref 的源码。
02/ref 的源码
代码来自于 vue.global.js ,调整了一下先后顺序。
function ref(value) { return createRef(value); } function createRef(rawValue, shallow = false) { if (isRef(rawValue)) { return rawValue; } return new RefImpl(rawValue, shallow); } class RefImpl { constructor(_rawValue, _shallow = false) { this._rawValue = _rawValue; this._shallow = _shallow; this.__v_isRef = true; this._value = _shallow ? _rawValue : convert(_rawValue); // 深层 ref or 浅层ref } get value() { track(toRaw(this), "get" /* GET */, 'value'); return this._value; } set value(newVal) { if (hasChanged(toRaw(newVal), this._rawValue)) { this._rawValue = newVal; this._value = this._shallow ? newVal : convert(newVal); trigger(toRaw(this), "set" /* SET */, 'value', newVal); } } } const convert = (val) => isObject(val) ? reactive(val) : val;
- ref 这是我们使用的函数,里面使用 createRef 来创建一个实例。
- createRef 做一些基础判断,然后进入主题,正式创建ref。这里还可以创建 shallowRef。
- RefImpl 这个才是主体,显然这是 ES6 的 class,constructor 是初始化函数,依据参数创建一个实例,并且设置实例的属性。这个和上面 ref 的打印结果也是可以对应上的。整个class的代码也是非常简单,设置几个“内部”属性,记录需要的数据,然后设置“外部”属性 value,通过setter、getter 实现对 value 的操作拦截,set 里面主要是 trigger 这个函数,由它调用模板的自动刷新的功能。
- convert 很显然,判断一下参数是不是 object,如果是的话,变成 reactive 的形式。这个就可以解释,引用类型的 ref 是如何实现响应性的,明显是先变成 reactive,然后在挂到 value 上面(挂之前判断一下是不是浅层的)。
03/ ref 和 reactive 的关系
通过打印结果的对比以及分析源码可以发现:
- 基础类型的 ref 和 reactive 没有任何关系。
- 引用类型的 ref ,先把 object 变成 reactive ,即利用 reactive 来实现引用类型的响应性。
关系就是这样的,千万不要再混淆了。
04/ shallowRef
浅层响应式,只监听 .value 的变化,真简单类型的响应式。
function shallowRef(value) { return createRef(value, true); // true 浅层 }
通过源码我们可以发现,在把引用类型挂到 value 之前,先判断一下是不是浅层的,如果是浅层的,并不会变成 reactive,而是直接把原来的对象挂在 value 上面,shallowRef 和 ref 的区别就在于这一点。
我们写几个实例看看效果:
setup () { // 浅层的测试 // 基础类型 const srefCount = shallowRef(0) console.log('refCount ', srefCount ) // 引用类型 const srefObject = shallowRef({ value: 0 }) console.log('refObject ', srefObject ) // 嵌套对象 const srefObjectMore = shallowRef({ info: {a: 'jyk'} }) console.log('shallowRef ', srefObjectMore ) // reactive 的 shallowRef const ret = reactive({name: 'jyk'}) const shallowRefRet = shallowRef(ret) console.log('shallowRefRet ', shallowRefRet ) // ==================== 事件 ================== // 修改基础类型 const setNumber = () => { srefCount.value = new Date().valueOf() console.log('srefCount ', srefCount ) } // 修改引用类型的属性 const setObjectProp = () => { srefObject.value.value = new Date().valueOf() console.log('srefObject ', srefObject ) } // 修改引用类型的value const setObject = () => { srefObject.value = { value: new Date().valueOf() } console.log('srefObject ', srefObject ) } // 修改嵌套引用类型的属性 const setObjectMoreProp = () => { srefObjectMore.value.info.a = new Date().valueOf() console.log('srefObjectMore ', srefObjectMore ) } // 修改嵌套引用类型的value const setObjectMore = () => { srefObjectMore.value = { qiantao: 1234567 } console.log('srefObjectMore ', srefObjectMore ) } // 修改reactive 的浅层ref const setObjectreactive = () => { shallowRefRet.value.name = '浅层的reactive' console.log('shallowRefRet ', shallowRefRet ) } }
看看结果:
浅层的ref
测试了一下响应性:
- 基础类型 srefCount 有响应性;
- 引用类型 srefObject 的属性没有响应性,但是直接修改 .value 是有响应性的。
- 嵌套的引用类型 srefObjectMore ,属性和嵌套属性都是没有响应性的,但是直接修改 .value 是有响应性的。
- reactive 套上 shallowRef ,然后修改 shallowRef.value.属性 = xxx ,也是可以响应的,所以浅层的ref 也不绝对,还要看内部结构。
05/ triggerRef
手动执行与 shallowRef 关联的任何效果。
官网的中文版里面写的很绕,其实就是 让 shallowRef 原本不具有响应性的部分,具有响应性。shallowRef 是浅层的,深层部分是没有响应性的,那么如果非得让这部分也具有响应性呢?这时候可以用 triggerRef 来实现。好吧,目前还没有想到有啥具体的应用场景,因为一般都直接简单粗暴的用 ref 或者 reactive 了,全都自带响应性。
测试了各种情况,发现 triggerRef 并不支持 shallowReactive,还以为能支持呢。(或许是我写的测试代码有问题吧,官网也没提 shallowReactive)
基于上面的例子,在适当的位置加上 triggerRef(xxx)就可以了。
setup () { // 引用类型 const srefObject = shallowRef({ value: 0 }) // 嵌套对象 const srefObjectMore = shallowRef({ value: {a: 'jyk'} }) // reactive 的 shallowRef const ret = reactive({name: 'reactive'}) const shallowRefRet = shallowRef(ret) // 浅层的reactive const myShallowReactive = shallowReactive({info:{name:'myShallowReactive'}}) const setsRet = () => { myShallowReactive.info.name = new Date().valueOf() triggerRef(myShallowReactive) // 修改后使用,不支持 } // ==================== 事件 ================== // 修改引用类型的属性 const setObjectProp = () => { srefObject.value.value = new Date().valueOf() triggerRef(srefObject) // 修改后使用 } // 修改引用类型的value const setObject = () => { srefObject.value = { value: new Date().valueOf() } triggerRef(srefObject) } // 修改嵌套引用类型的属性 const setObjectMoreProp = () => { srefObjectMore.value.value.a = new Date().valueOf() triggerRef(srefObjectMore) } // 修改嵌套引用类型的value const setObjectMore = () => { srefObjectMore.value.value = { value: new Date().valueOf() } triggerRef(srefObjectMore) } // 修改reactive 的浅层ref const setObjectreactive = () => { shallowRefRet.value.name = '浅层的reactive' + new Date().valueOf() triggerRef(shallowRefRet) } return { srefObject, // 引用类型 srefObjectMore, // 嵌套引用类型 shallowRefRet, // reactive 的浅层ref myShallowReactive, // 浅层的reactive setsRet, // 修改浅层的reactive setObjectProp, // 修改引用类型的属性 setObject, // 修改引用类型的value setObjectMoreProp, // 修改嵌套引用类型的属性 setObjectMore, // 修改嵌套引用类型的value setObjectreactive // 试一试reactive的浅层ref } }
深层部分,不使用 triggerRef 就不会刷新模板,使用了 triggerRef 就可以刷新模板。话说,为啥会有这个函数?