1.改写计数器例子:
Vue2中的代码:
<template>
<p>{{count}}</p>
<button @click="increase">👍</button>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
export default defineComponent({
name: 'App',
data(){
return {
count:0
}
},
methods:{
increase(){
this.count++;
}
}
});
</script>
使用vue3改写后的代码:
setup:在生命周期之前运行的- 通过代码可以发现,
increase函数改变count的值是改变的count对象的value,但template中展示值是直接通过count展示,而不是count.value computed:是一个函数,接收一个参数:回调函数,回调函数中处理想要返回的值
<template>
<p>{{count}}</p>
<p>{{double}}</p>
<button @click="increase">👍</button>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import {ref,computed} from "vue";
export default defineComponent({
name: 'App',
setup(){
const count = ref(0);
const increase = () => {
count.value ++;
}
const double = computed(()=>{
return count.value * 2;
})
return {
count,increase,double
}
}
});
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
结果如下:
2.更进一步,reactive
1.使用reactive实现计数器:
-
reactive:reactive是一个函数,参数是一个对象;通过reactive包装后返回一个响应式的数据data -
因为
computed的原因,需要使用interface去约束下data的类型,否则如下图:上图的
data会报错,解决方案就是添加interface去约束,代码如下:
<template>
<p>{{data.count}}</p>
<p>{{data.double}}</p>
<button @click="data.increase">👍</button>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import {ref,computed,reactive} from "vue";
interface DataProps {
count:number;
double:number;
increase:() => void;
}
export default defineComponent({
name: 'App',
setup(){
const data:DataProps = reactive({
count:0,
increase:()=>{
data.count++;
},
double:computed(()=>{
return data.count * 2
})
})
return {
data
}
}
});
</script>
2.无效的优化方案:
这里面看到data.count...等的很麻烦,想要在return {data}中对data进行解构或者es6的"..."展开操作符,但是解构后发现页面计数器失效,因为直接通过下面的方式:
//略
//页面点击无反应
return {
...data
}
//或者
//页面点击后依然无反应
return {
count:data.count,
increase:data.increase,
double:data.double
}
3.正确使用toRefs去优化:(响应式对象——->普通对象)
-
toRefs:使用一个reactive对象作为参数,返回一个普通的对象 -
通过
toRefs转化后的refData如下图,是一个Ref类型,然后展开refData即可。 -
代码如下:
<template>
<p>{{count}}</p>
<p>{{double}}</p>
<button @click="increase">👍</button>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import {ref,computed,reactive,toRefs} from "vue";
interface DataProps {
count:number;
double:number;
increase:() => void;
}
export default defineComponent({
name: 'App',
setup(){
const data:DataProps = reactive({
count:0,
increase:()=>{
data.count++;
},
double:computed(()=>{
return data.count * 2
})
})
const refData = toRefs(data);
return {
...refData
}
}
});
</script>
3.ref和reactive的区别:
上面使用了变量声明、ref、以及reactive三种方式去定义变量:
用ref定义的数据,打印结果是一个被对象包裹的响应的数据,使用reactive的方式和纯变量声明的方式打印结果是一样的,这是什么原因呢?
在控制台输出一个警告信息, 提示100这个值不能被reactive创建。
官方也推荐我们在定义数据的时候,reactive定义复杂的数据类型的数据,ref推荐定义基本数据类型,所以如果要使用reactive定义基本数据类型的话,需要在reactive中将数据包装一下。
- 但是使用
reactive定义的数据和ref定义的数据打印结果有一些差异:
ref定义的数据打印结果需要.value才能获取到结果,而reactive则不需要
总结:
reactive和ref都是用来定义响应式数据的。reactive更推荐去定义复杂的数据类型,不能直接解构,需要需要使用toRefs()转成普通对象后解构;ref更推荐定义基本类型ref和reactive本质可以简单地理解为ref是对reactive的二次包装,ref定义的数据访问的时候要多一个.value- 使用
ref定义基本数据类型,ref也可以定义数组和对象。
4.对比vue2和vue3的响应式
vue2的响应式:
-
通过
object.defineproperty()实现响应式的,由于这个方法的限制,对于对象上新增的属性是无能为力的; 对于数组则需要拦截它的原型方法来完成响应式 -
对于对象:
-
对于数组:
为此vue中还可以使用vm.$set方法。官网链接:cn.vuejs.org/v2/guide/re…
vue3的响应式:
-
利用了
proxy实现的响应式如果有一个
data对象,那么使用defineProperty和Proxy拦截如下
// defineProperty
Object.defineProperty(data,"name",{
get(){},
set(){},
})
//Proxy
new Proxy(data,{
get(key){},
set(key,value){}
})
defineProperty是针对某个明确的属性的,对于新增的属性无能为力Proxy更像是从更高的维度去,针对于整个data对象的。