Vue3与Vue2的区别(组合式API)
目录
vue3中watch函数对reactive的响应式数据的监听
1、响应式数据
在vue2中,vue向我们承诺data中的数据,即使是嵌套很多层的对象,他也是响应式的数据。
在vue3中,数据和方法都是在setup中定义返回的,这种直接赋值的写法无法做到数据的响应式
export default {
name: 'App',
setup() {
let a='aaaa'
function show(){
console.log(a);
}
return {a,show}
}
}
此时我们需要引用vue中的ref和reactive函数。
通常基本数据类型用ref实现数据的响应式,经过ref函数包裹的数据,会返回一个RedImpl,是一个对象,它里面用了我们熟悉的vue2的数据代理的方法,在原型上有get set函数,实现了数据的响应式。但是当我们需要获取值的时候需要用过 .value才能获取到他的值
对象这种引用数据类型往往用reactive函数实现数据的响应式,返回的是一个ES6中Proxy的对象,对象里就是我们定义的值,且不需要通过value的方式取值
(在模板中是不需要.value来取值渲染页面的)
<script>
import {ref,reactive} from 'vue'
export default {
name: 'App',
setup() {
let a=ref('aaaa')
let b=reactive({
city:'南京',
word:'前端'
})
function show(){
console.log(a,b);
}
return {a,b,show}
}
}
</script>
2、vue2和vue3的响应式原理
在vue2中,通过Object.defineProperty()来进行劫持各个属性的getter,setter,再结合发布订阅模式,在数据变动的时候,发布消息给订阅者,如何更新数据渲染页面的。
但是这样做的话是有缺点的,直接通过obj.a这种方法给对象添加key value值或者是通过数组下标操作数据的话,vue是监听不到的,所以在vue中提供了set和delete方法来弥补这个缺点。
在vue3中,不再用Object.defineProperty(),而是使用了ES6的一个新的构造函数Proxy,Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。Proxy 这个词的原意是代理,用在这里表示由它来“代理”某些操作,可以译为“代理器”。
<script>
const p = {
a: 1,
b: 2
}
let obj = new Proxy(p, {
get: function(target, propKey) {
console.log(target, propKey);
return Reflect.get(target, propKey);
},
set: function(target, propKey, value) {
return Reflect.set(target, propKey, value);
},
deleteProperty: function(target, propKey) {
return Reflect.deleteProperty(target, propKey)
}
});
</script>
Proxy可以接收两个参数,第一个参数就是进行代理的对象,第二个参数就是拦截外界对数据访问的方法的配置,get方法可以获取到两个参数,通过打印我们可以得到target就是我们需要进行代理的那个对象,propKey就是我们读取的那个Key值,所以在这个函数里有了这两个参数,就可以对具体操作的数据进行拦截,set中的value就是具体的那个新值,如果源对象中有那个key就是进行修改,没有就是增加。vue3中还提供了deleteProperty方法,可以监听到对数据的删除操作,这样在vue3中我们直接对对象进行增删改查的操作,就可以直接被vue监听到,实现响应式数据,弥补了vue2数据劫持的缺点。
3、计算属性Computed
在vue2中通常都是这样去写我们的计算属性(简写形式)
data() {
return {
a:1,
b:2
}
},
computed:{
sum(){
return this.a+this.b
}
}
但是在vue3中,我们首先需要引入computed这个方法,然后通过函数调用的形式计算出返回值,并且要把这个返回值return出去
<template>
<h1>{{fullAddress}}</h1>
</template>
<script>
import {reactive,computed} from 'vue'
export default {
name: 'App',
setup(props,context) {
let data=reactive({
province:'江苏',
city:'南京'
})
let fullAddress=computed(()=>{
return data.province+data.city
})
return {data,fullAddress}
},
}
</script>
4、监听函数Watch
vue3中watch函数对ref的响应式数据的监听
<template>
<button @click="a++">a++</button>
</template>
<script>
import {reactive,watch, ref} from 'vue'
export default {
name: 'App',
setup(props,context) {
let a=ref(1)
let b=ref(3)
watch(a,(newValue,oldValue)=>{
console.log('a在变化',newValue,oldValue);
},{immediate:true})
return {a,b}
},
}
</script>
当我们通过watch对ref定义的响应式数据监听的时候,第一个参数就是要监听的对象,第二个参数为一个函数可以接受到新值和旧值,并进行逻辑的处理,第三个参数是watch的一些配置,可以开启动immediate初始监听,和deep深度监听。
如果我们把watch的第一个参数写为一个数组,就可以监听到数组内数据的变化
vue3中watch函数对reactive的响应式数据的监听
<template>
<button @click="a.b++">b++</button>
</template>
<script>
import {reactive,watch, ref} from 'vue'
export default {
name: 'App',
setup(props,context) {
let a=reactive({
b:1,
c:{
d:2
}
})
watch(a,(newValue,oldValue)=>{
console.log('b在变化',newValue,oldValue);
},{immediate:true})
return {a}
},
}
</script>
如果对reactive定义的响应式数据进行监听,此时默认开启了deep:true,而且如果在第三个参数进行了deep:false的配置,此时是无效的。
但是这个监听会出现一个bug,就是获取不到oldValue的值,如图打印中oldValue的值跟newValue的值是一样的。
watch(()=>a.b,(newValue,oldValue)=>{
console.log('b在变化',newValue,oldValue);
},{immediate:true})
监听reactive定义的响应式数据的时候如果第一个参数为对象中一个具体的某一个属性,我们需要用函数返回值的形式,返回这个属性作为参数。
<script>
import {reactive,watch, ref} from 'vue'
export default {
name: 'App',
setup(props,context) {
let a=reactive({
b:1,
c:{
d:2
}
})
watch(()=>a.c,(newValue,oldValue)=>{
console.log('d在变化',newValue,oldValue);
},{immediate:true,deep:true})
return {a}
},
}
</script>
此时我想操作c对象里面的d,但是我们第一个参数范围的是c,此时我们就需要配置deep:true开启深度监听才能进行操作。
总结
当我们监听ref的响应式数据的时候 oldValue可以正常显示
当我们监听reactive的响应式数据的时候哦,除非参数为具体的一个属性,否则oldValue是等于newValue的。且需要用函数返回值的形式作为参数。
当我们需要监听多个对象的时候,需要用数组。
vue3中还添加了一个函数watchEffect,他只会监听他的回调函数的函数体内引用到的变量,类似computed,但不需要有返回值,computed注重结果,返回的结果,watchEffect注重过程。
5、vue3的生命周期
在vue3中,没有了beforeDestory和destoryed,取而代之的是beforeUnmount和Unmounted,
在v3 setup里想写生命周期函数的话要在前面加上on,并且没有了beforecreate和created,setup就相当于以及created了,在setup之外可以正常使用vue3的生命周期函数
export default {
name: 'App',
setup() { //在组合式API里没有了beforecreate和created setup取代了他们两
onBeforeMount(()=>{}),
onMounted(()=>{}),
onBeforeUpdate(()=>{}),
onUpdated(()=>{}),
onBeforeUnmount(()=>{}),
onUnmounted(()=>{})
}
}
export default {
name: 'App',
setup() {
},
beforeCreate() {},
created() {},
beforeMount() {},
mounted() {},
beforeUpdate() {},
updated() {},
beforeUnmount() {},
unmounted() {},
}
6、hooks函数
类似于vue2中的mixin,可以在hook里调用生命周期函数以及各种api形成组合式的API,在想用的地方进行引入,和调用。
用于封装函数,提高代码的复用性。
7、toRef和toRefs
<template>
<h1>vue3 {{study.qianduan}}</h1>
<h1>vue3 {{study.houduan.one}}</h1>
<h1>vue3 {{study.houduan.two}}</h1>
</template>
<script>
import { reactive } from 'vue'
export default {
name: 'App',
setup() {
let study=reactive({
qianduan:'js',
houduan:{
one:'java',
two:'python'
}
})
return {
study
}
},
}
</script>
如图所示,我们直接return出去一个reactive的响应式的对象,这样在插值语法书写的时候就会很繁琐,我们可以用toRef让对象中的一个属性值变为Ref对象,使其成为响应式的数据
<h1>vue3 {{js}}</h1>
const js=toRef(study,'qianduan')
return {
study,js
}
这样的话我们就可以在页面中直接打印出 'qianduan'中的js值
但是如果我们想批量的进行生产,就需要用到toRefs,此时jsx就成功代理了study中的所有数据,再结合拓展运算符就可以让页面插值语法中的变量写的简洁,这样就符合了vue中的代码风格!
也可以再页面中成功打印
<template>
<h1>vue3 {{qianduan}}</h1>
<h1>vue3 {{houduan.one}}</h1>
<h1>vue3 {{houduan.two}}</h1>
</template>
<script>
import { reactive, toRefs } from 'vue'
export default {
name: 'App',
setup() {
let study=reactive({
qianduan:'js',
houduan:{
one:'java',
two:'python'
}
})
const jsx=toRefs(study)
return {
...jsx
}
},
}
</script>
8、readonly与shallowReadonly
readonly:让一个响应式数据变成只读的(deep:true)
shallowReadonly:让一个响应式数据变成只读的(deep:false)
9、toRaw与MarkRaw
toRaw:将一个reactive定义的响应式数据转化为不是响应式的对象
MarkRaw:让一个对象,永远不能成为响应式的数据,通常给第三方库添加。
10、组合式API的优势
在vue2的项目中,数据都在data里,方法都在methods里,所以有时候的大型项目中,一个组件可能有很多的方法和数据,从折叠状态展开后,会很难查找。
但是vue3的组合式API,一个业务需要的数据方法,生命周期函数都在一起,配合hook函数的使用,能让代码的复用性,条理性都很高,维护起来也很方便。
但是不代表vue2的选项式API不能用,在中小项目中,vue2的选项式API也是不错的选择,但是在大型项目中,vue3的组合式API会表现的更加优秀。