书接上文,我们继续探讨这个话题,我还是先把官网的截图甩在这里。
问题思考
- 解构的值是基本数据类型,会丢失响应式,那如果是引用数据类型,会保持响应式吗?
- 丢失响应式是
vue
内部帮我们做的吗?
问题探究
我们先写例子来构造下场景,比较简单。
<script setup>
import { reactive } from "vue";
const state = reactive({
obj: {
name: "zhangsan",
},
});
const handleClick = () => {
const { obj } = state;
obj.name = "lisi";
};
</script>
<template>
<button @click="handleClick">点击切换</button>
<div>{{ state }}</div>
</template>
复制代码
效果如下:
下面我们改造下代码:
const handleClick = () => {
const { name } = state.obj;
name = "lisi";
};
复制代码
效果如下:
经过上面比对后发现,当解构出来的是一个引用对象类型时,它是响应式的,当时基本数据类型时,响应式会丢失。
源码分析
我们在最上面代码打上debug,然后我们进入到 reactive
方法里面,它接收一个 target
,然后判断是否是只读的,最后返回的是 createReactiveObject
方法调用的返回值,我们进入到这个方法里面。
在这个方法里面,做了一些判断,首先判断是否是对象,如果不是,返回该值。
再判断标识 __v_raw
是否已经是一个被代理的对象了,如果是则返回。
接下来的判断 existingProxy
对已经Proxy的,则直接从 proxyMap
数据结构中取出这个Proxy对象,这是提升性能的一种方法。从代码中可以看出后面会对Proxy对象会进行 proxyMap.set(target, proxy)
操作,这里的 proxyMap
调用的是 WeakMap
的实例。
接着往下走,后面还对可以reactive的对象加了个白名单,只有 Object,Array,Map,Set,WeakMap,WeakSet 对象可以进行 reactive 操作。
最后进行了 Proxy,在初次进行reactive的时候只对第一层进行Proxy。我们看下reactive后的Proxy对象
此时的 targetType
值是 1,所以会走 baseHandlers
,这个方法是 createReactiveObject
入参的第三个参数,也就是 mutableHandlers
,这里面包含了 Proxy 的常用方法
createGetter
就是调用 get
方法后执行的函数,在 447 行我们发现,如果子项仍是对象, 会递归调用 reactive
进行深层次代理,到这里,我们发现,源码没有对丢失响应式做任何事情,也就是说它失去响应式不在于Vue而是在于Proxy对象本身。
简单实现
const obj = {
count: 1
};
const proxy = new Proxy(obj, {
get(target, key) {
console.log("get");
return target[key]
},
set(target, key, value) {
console.log("set");
target[key] = value
return true;
}
});
console.log(proxy.count);
复制代码
当我们尝试读取值的时候会调用 get
方法,设置值的时候会调用 set
方法,如果 let { count } = proxy; count = 2
解构再赋值是不会触发 set
方法调用的。那如果是解构出来的是引用数据类型呢,现在我们改下上面代码:
const obj = {
counter: {
count: 1
}
};
const reactive = (target) => {
return new Proxy(obj, {
get(target, key) {
console.log("get");
if (typeof target[key] === 'object') {
return reactive(target[key]);
}
return target[key]
},
set(target, key, value) {
console.log("set");
target[key] = value
return true;
}
});
}
let {
counter
} = reactive(obj);
counter.count = 2
复制代码
这个时候,get
方法和 set
方法都会被调用,这也证实了我们上面的结论,