响应式设计是Vue中比较核心的设计,Vue2升级到Vue3之后响应式的核心实现由Object.defineProperty
换成Proxy
。这是为什么呢?
Object.defineProperty 实现响应式的问题
Vue2 的官方文档有这样一段话:
这是什么意思呢!我们来看个小案例
js
<template>
<div id="app">
<p v-for="(val, key) in info" :key="key">{{ val }}</p>
<button @click="addGender">增加性别</button>
<button @click="deleteGender">删除地址信息</button>
<p v-for="(val, index) in list" :key="index">{{ val }}</p>
<button @click="updateList">更新手机列表</button>
</div>
</template>
<script>
export default {
data() {
return {
info: {
name: "hello",
address: "深圳",
},
list: ["小米", "华为"],
};
},
methods: {
addGender() {
this.info.gender = "男";
console.log(this.info, "添加之后");
},
deleteGender() {
delete this.info.address;
console.log(this.info, "删除之后");
},
updateList() {
this.list[2] = "oppo";
this.list[0] = "vivo";
console.log(this.list, "添加和更新之后");
},
},
};
</script>
<style lang="scss"></style>
运行效果:
点击下增加性别
可以看到,控制台打印的数据已经更新了,有了性别属性,但是视图并没有更新。
现在我们来点击下删除地址信息按钮:
可以看到,控制台的数据已经删除了地址信息,但是视图并没有更新,还是显示了地址信息。
点击下更新手机列表:
可以看到,在控制台的数据里面,新增了一个oppo, 并且将手机列表的第一个改成了vivo, 但是视图并没有同步更新。
通过这个小案例,相信你已经看到了在Vue2中响应式存在的问题,这就是Object.defineProperty
的缺陷,为什么Object.defineProperty
会有这个缺陷呢? 因为Object.defineProperty
是对定义的属性进行拦截的。看看Object.defineProperty
的使用就知道了:
js
const data = {
name: "hello",
address: "深圳",
};
const newData = { ...data };
Object.defineProperty(newData, "name", {
get() {
console.log("getter");
return data.name;
},
set(key, newVal) {
console.log("setter", key);
data[key] = newVal;
},
});
console.log(newData.name);
newData.name = "world";
console.log(newData.address);
newData.address = "上海";
在这段代码中,我们只对name 在Object.defineProperty
中进行了定义,所以只有name 属性读取和设置才会触发getter 和setter, address 的读取和设置不会触发getter和setter
Vue3 响应式的解决方案
为了更好的解决这些问题,于是在Vue3 中将响应式的实现改成了Proxy
。 在Vue3 是否是真的解决了这些问题了呢, 我们将之前的的小案例改成Vue3来写。
js
<template>
<div id="app">
<p v-for="(val, key) in info" :key="key">{{ val }}</p>
<button @click="addGender">增加性别</button>
<button @click="deleteGender">删除地址信息</button>
<p v-for="(val, index) in list" :key="index">{{ val }}</p>
<button @click="updateList">更新手机列表</button>
</div>
</template>
<script setup>
import { reactive, toRefs } from "vue";
const data = reactive({
info: {
name: "hello",
address: "深圳",
},
list: ["小米", "华为"],
});
const addGender = () => {
data.info.gender = "男";
console.log(data.info, "添加之后");
};
const deleteGender = () => {
delete data.info.address;
console.log(data.info);
};
const updateList = () => {
data.list[2] = "oppo";
data.list[0] = "vivo";
console.log(data.list);
};
const { info, list } = toRefs(data);
</script>
<style lang="scss"></style>
点击增加性别:
可以看到控制台的数据和视图都同步新增了性别字段。
点击删除地址信息:
可以看到控制台的数据和视图都同步删除了地址信息
点击更新手机列表:
可以看到控制台数据和视图都将列表的第一条数据改成了vivo, 并且新增了一条数据oppo。这就是Proxy 的魅力。为什么Proxy 能解决这个问题呢。因为Proxy代理的是整个对象,而不是一个属性。来看看Proxy 的使用就明白了。
js
const data = {
name: "hello",
address: "深圳",
};
const proxy = new Proxy(data, {
get(target, key) {
console.log("getter", key);
return Reflect.get(target, key);
},
set(target, key, newVal) {
console.log("setter", key);
Reflect.set(target, key, newVal);
return true;
},
});
console.log(proxy.name);
proxy.name = "world";
console.log(proxy.address);
proxy.address = "上海";
proxy.gender = "男";
console.log(proxy.gender);
可以看到无论是之前定义的属性还是新增的属性都能触发gettter 和setter, 因为Proxy
代理的是整个对象而不是某个属性。
总结
Vue2升级到Vue3将Object.defineProperty
换成Proxy
的主要原因是解决一些历史问题,比如对象新增属性,删除属性不能更新视图问题。还有数组通过下标新增和修改数据无效等问题