Vue(Vue2+Vue3)——80-83常用的Composition(组合)API
CompositionAPI也叫组合式API
官方文档: https://v3.cn.vuejs.org/guide/composition-api-introduction.html
1 初识setup
setup是vue3的入门技术,想要学习vue3,最好从它开始,因为setup是所有Composition API(组合API)表演的舞台。如果没有setup,那么其他的组合式API都没地方写
setup是Vue3.0中一个新的配置项,值为一个函数,组件中所用到的:数据、方法等等,均要配置在setup中,它有两种返回值。对象和渲染函数,下面通过案例一一说明使用
注意:此处的案例只是测试一下setup,暂时不考虑响应式的问题。
返回对象(重点关注)
如果setup返回一个对象,则对象中的属性、方法, 在模板中均可以直接使用,这种返回方式也是用的比较多的一种
比如这里我定义了一些数据和方法。返回对象的时候如果key和value一致,那么可以只保留key
使用的时候直接进行插值语法获取和事件绑定即可,这里比vue2多了一步:需要手动return方法或数据
数据肯定是可以通过setup返回对象的方式获取到的
返回渲染函数(了解即可)
setup除了可以返回对象,还可以返回渲染函数,可以自定义渲染内容,这种返回方式相对来说用的比较少(了解即可)
在渲染之前需要手动引入
引入渲染h
vue3一个很大的特点就是使用什么就直接引入
这个h vue3已经帮助我们封装好了,直接引入并使用即可
// 引入渲染h import {h} from 'vue'
细节问题
虽然在vue3中可以写一个vue2相关的配置,包括data、methos、computed...但是并不推荐在vue3中写vue2相关代码,因为虽然vue2可以访问到setup中的属性、方法,但是但在setup中不能访问到Vue2配置的data、methos、computed,另外如果有重名, setup优先
setup总结
- 理解:Vue3.0中一个新的配置项,值为一个函数。
- setup是所有Composition API(组合API)“ 表演的舞台 ”。
- 组件中所用到的:数据、方法等等,均要配置在setup中。
- setup函数的两种返回值:
- 若返回一个对象,则对象中的属性、方法, 在模板中均可以直接使用。(重点关注!)
- 若返回一个渲染函数:则可以自定义渲染内容。(了解)
- 注意点:
- 尽量不要与Vue2.x配置混用
- Vue2.x配置(data、methos、computed...)中可以访问到setup中的属性、方法。
- 但在setup中不能访问到Vue2.x配置(data、methos、computed...)。
- 如果有重名, setup优先。
- setup不能是一个async函数,因为返回值不再是return的对象, 而是promise, 模板看不到return对象中的属性。(后期也可以返回一个Promise实例,但需要Suspense和异步组件的配合)
2 ref(引用)函数
此ref不同于vue2中元素中的ref属性,在vue2中它用来定义给元素定义ref属性控制元素的操作,类似于js的id,但是在vue3中它用于定义一个响应式的数据
它可以定义普通数据和对象类型的数据,两者之前有些不同,下面通过案例进行一一说明
案例演示(处理基本类型)
新增一个按钮,对已有的信息进行更改
发现问题
理论来说代码是没问题的,但是点击按钮数据不变,但是控制台也没有报错
为了验证是否修改成功,这边打印下
看起来数据是修改成功的,但是vue没有检测到数据的改变
问题是因为定义的name和age都是普通的字符串,不是响应式的数据,所以vue是不会检测到的,那么问题来了,如果把普通的数据定义成vue能监测到的响应式的数据呢?
这时候就要使用ref了
引入ref对象
使用ref之前,需要引入
// 引入ref对象 import {ref} from "vue";
使用ref对象
ref对象使用起来特别的简单,直接使用ref(xxx)即可
这时候案例来说就可以了,我们再次测试下
发现还是不行
refImpl(引用对象)
这是因为修改的方式不对,可以看到被ref修饰后的属性变成了一个refImpl对象
refImpl这个单词拆分的话可以这么理解,ref(reference)引用,impl(implment)实现,专业话语叫:引用对象
它也有getter和setter,并且我们可以通过vulue修改属性的值
所以如果想要修改属性,可以使用value进行修改
再次修改
一个奇怪的现象
案例来说,插值语法读取属性的时候,也是应该通过xxx.value获取的,但是如果这么写就获取不到
这是因为:在模板里面,不需要手动的.value的方式获取,vue3会自动帮助我们解析,如果发现是一个ref对象,就会自动帮我们获取.value属性
案例演示(处理对象类型)
除了可以处理基本类型,ref也支持处理对象类型,接下来升级一下案例,演示使用ref处理对象类型
定义一个对象类型的属性
把数据展示页面上
接下来提出需求,修改信息的时候把工作种类和薪水都改下,如果按照上面的写法使用.value获取,那就错了
如果使用ref传入对象,想要拿到属性就不用.value了
这说明,ref对基本数据类型直接使用的get和set,但是对于对象类型性来说,用的是ES6中的proxy
ref总结
- 作用: 定义一个响应式的数据
- 语法:
const xxx = ref(initValue)
- 创建一个包含响应式数据的引用对象(reference对象,简称ref对象)。
- JS中操作数据:
xxx.value
- 模板中读取数据: 不需要.value,直接:
<div>{{xxx}}</div>
- 备注:
- 接收的数据可以是:基本类型、也可以是对象类型。
- 基本类型的数据:响应式依然是靠
Object.defineProperty()
的get
与set
完成的。 - 对象类型的数据:内部 “ 求助 ” 了Vue3.0中的一个新函数——
reactive
函数。
3 reactive(响应式)函数
它和ref类似,也是把数据定义成响应成数据
注意:它不同于ref,只能定义对象类型的数据,不能定义基本类型的数据,所以说基本类型数据要使用ref定义,对象类型数据最好使用reactive函数
那么首先就可以验证只能使用reavtive定义基本类型数据这一说了
引用reactive
和ref一样,直接引用即可
// 引入ref和reactive函数 import {ref,reactive} from "vue";
接下来我使用reactive定义一个普通数据
控制台直接报错了
所以应该改成一个对象类型的数据,如果使用了reactive定义了,获取属性的时候直接获取即可,就不再使用.value了
效果都是一样的
验证:深层次处理数据
reactive是处理对象类型的响应式的时候,是一个深层次的
可以定义一个套娃式的对象,使用的使用直接套就行
验证:处理数组类型数据
首先定义一个数据类型的数据
这时候点击修改,数据没有变,但是其实已经修改了
之所以没有变是因为vue没有检测到,所以要定义成响应式的
再次测试下,发现已经改了,说明reactive也是支持数组的
reactive定义数据技巧
如果非要使用reactive定义普通类型的数据,可以这么玩,把组件种用到的数据封装到一个对象里面,然后把这个对象交给reavtive修饰变成一个响应式对象或代理对象
效果也是一样的
reactive总结
- 作用: 定义一个对象类型的响应式数据(基本类型不要用它,要用
ref
函数) - 语法:
const 代理对象= reactive(源对象)
接收一个对象(或数组),返回一个代理对象(Proxy的实例对象,简称proxy对象) - reactive定义的响应式数据是“深层次的”。
- 内部基于 ES6 的 Proxy 实现,通过代理对象操作源对象内部数据进行操作。
4 ref对比reactive
- 从定义数据角度对比:
- ref用来定义:基本类型数据。
- reactive用来定义:对象(或数组)类型数据。
- 备注:ref也可以用来定义对象(或数组)类型数据, 它内部会自动通过
reactive
转为代理对象。
- 从原理角度对比:
- ref通过
Object.defineProperty()
的get
与set
来实现响应式(数据劫持)。 - reactive通过使用Proxy来实现响应式(数据劫持), 并通过Reflect操作源对象内部的数据。
- 从使用角度对比:
- ref定义的数据:操作数据需要
.value
,读取数据时模板中直接读取不需要.value
。 - reactive定义的数据:操作数据与读取数据:均不需要
.value
5 set的两个注意点
执行的时机
setup执行的时机是非常早的,它甚至比beforeCreate还要早,并且this是undefined而不是实例对象
setup接收到的参数
通过测试得知,setup可以接受到两个参数
参数1:props:用于组件传递数据
我们可以通过组件传递数据测试:
这时候出现了警告:
意思是给组件传递了数据但是缺没有接收数据,这个警告在vue2中是没有的
想用的话需要声明接收,这时候再看警告就没了,并且props里面接收到了参数
如果接收一个不存在的数据,也不会报错,只是会呈现undefined
参数2:context
说完了第一个参数,再说说第二个参数,在官方文档里面叫做context(上下文)
它里面主要有三个属性需要我们注意
1:attrs: 值为对象,包含:组件外部传递过来,但没有在props配置中声明的属性, 相当于 this.$attrs
。
2:slots: 收到的插槽内容, 相当于 this.$slots
。
3:emit: 分发自定义事件的函数, 相当于 this.$emit
。
set的两个注意点总结
- setup执行的时机
- 在beforeCreate之前执行一次,this是undefined。
- setup的参数
- props:值为对象,包含:组件外部传递过来,且组件内部声明接收了的属性。
- context:上下文对象
- attrs: 值为对象,包含:组件外部传递过来,但没有在props配置中声明的属性, 相当于
this.$attrs
。 - slots: 收到的插槽内容, 相当于
this.$slots
。 - emit: 分发自定义事件的函数, 相当于
this.$emit
。
6 computed计算属性
与Vue2中computed配置功能一致,都是通过已有的数据算出点新东西
案例演示
vue3中也是支持vue2中编写计算属性的方式的,但是不推荐(都用vue3了,还写啥vue2啊!)
下面通过案例演示分别演示vue2和vue3的写法
vue2实现
vue3实现
vue中把computed
变成了组合式的API,所以首先要引入computed
引入computed
// 引入computed计算属性 import {computed} from "vue";
简写形式
计算属性也是属性,也是可以被修改的,简写形式并不能支持被修改,如果被修改会报警错
这时候就要用到完整形式了
完整形式
和vue2是一样的,使用get和set编写逻辑
案例演示
计算属性总结
- 与Vue2中computed配置功能一致
- 写法:
import {computed} from 'vue' setup(){ ... //计算属性——简写 let fullName = computed(()=>{ return person.firstName + '-' + person.lastName }) //计算属性——完整 let fullName = computed({ get(){ return person.firstName + '-' + person.lastName }, set(value){ const nameArr = value.split('-') person.firstName = nameArr[0] person.lastName = nameArr[1] } }) }
7 watch(监视)函数
vue3的数据监视和vue2配置功能是一样的,都是对变化的数据进行监视
案例展示
vue3中也是支持vue2中编写数据监视方式的,但是不推荐(都用vue3了,还写啥vue2啊!)
下面通过案例演示分别演示vue2和vue3的写法
vue2实现
分为简单写法和完整写法
简单形式
直接把要监视的数据写成一个函数,会有两个参数,新值和老值
完整形式
完整形式要把监视的属性写成一个对象,而不是函数,对象里面有很多的配置,其中最重要的是handler,它同样可以接受到新值和老值两个参数
它相比于简单形式,优势在于可以配置更多的属性,比如立即监听(immediate),深度监视(deep)等
vue3实现
以上无论是简单形式还是完整形式,都只是vue2中的写法
在vue3中,watch和computed一样,变成了一个组合式的API,使用的时候需要先引入。
引入watch
// 引入watch函数 import {watch} from "vue";
引入完成之后就可以使用了,在vue3中使用watch十分简单,但是有一些不同的使用场景和坑,下面说明
情况1:监视定义ref定义的一个响应式数据
情况2:监视定义ref定义的多个响应式数据
这时候新增一个需求,再定义一个ref响应式数据进行监听
最笨的办法就行,再写一个watch监听,把监听的数据写成info
这也是vue3和vue2的差距,在vue2中怎么可能写多个watch呢,只能写一个watch配置项,但是在vue3中就可以写多个watch函数
但是这种写法较为麻烦,有一个简单的写法,只写一个watch函数,监视的值为一个数组:
watch中加入属性
现在想在watch中加入一些属性,比如自动监听,这时候watch的第三个参数的作用就体现出来了,就是专门给我们加入监听的属性用的
watch的三个参数:
参数1:监视数据(属性),参数2:回调函数,参数3:配置属性
一上来就进行数据监视了,说明属性生效了
情况3:监视reactive定义的一个响应式数据全部属性
刚才监视的都是ref普通数据类型,如果使用reactive监视对象类型的数据,会有一些坑,下面演示一下
坑1:无法获取到正确的oldValue
注意:这里有一个坑,可以发现不管是姓名还是年龄,改变前后都一样,无法获取到正确的oldValue,而且这个问题目前无法解决
坑2:强制开启了深度监视且无法关闭
vue3默认(强制)开启了deep(深度监视)并且无法关闭
可以看到即使把deep设置为false,也是可以监视到的
既然deep属性关不了,那么肯定是影响效率的,所以就有了下面这种情况
情况4:监视reactive定义的一个响应式数据中的某一个属性
我们可以只监视对象中的某一个数学,可以使用函数,返回值就是这个属性:
情况5: 监视reactive定义的一个响应式数据中的某些属性
这种写法稍微复杂点,使用数组包裹起来要监视的属性即可:
特殊情况:
如果要监视reactive里面的多层次对象,需要开启deep:true:
watch函数总结
- 与Vue2.x中watch配置功能一致
- 两个小“坑”:
- 监视reactive定义的响应式数据时:oldValue无法正确获取、强制开启了深度监视(deep配置失效)。
- 监视reactive定义的响应式数据中某个属性时:deep配置有效。
//情况一:监视ref定义的响应式数据 watch(sum,(newValue,oldValue)=>{ console.log('sum变化了',newValue,oldValue) },{immediate:true}) //情况二:监视多个ref定义的响应式数据 watch([sum,msg],(newValue,oldValue)=>{ console.log('sum或msg变化了',newValue,oldValue) }) /* 情况三:监视reactive定义的响应式数据 若watch监视的是reactive定义的响应式数据,则无法正确获得oldValue!! 若watch监视的是reactive定义的响应式数据,则强制开启了深度监视 */ watch(person,(newValue,oldValue)=>{ console.log('person变化了',newValue,oldValue) },{immediate:true,deep:false}) //此处的deep配置不再奏效 //情况四:监视reactive定义的响应式数据中的某个属性 watch(()=>person.job,(newValue,oldValue)=>{ console.log('person的job变化了',newValue,oldValue) },{immediate:true,deep:true}) //情况五:监视reactive定义的响应式数据中的某些属性 watch([()=>person.job,()=>person.name],(newValue,oldValue)=>{ console.log('person的job变化了',newValue,oldValue) },{immediate:true,deep:true}) //特殊情况 watch(()=>person.job,(newValue,oldValue)=>{ console.log('person的job变化了',newValue,oldValue) },{deep:true}) //此处由于监视的是reactive素定义的对象中的某个属性,所以deep配置有效
8 watchEffect()函数
watchEffect()函数是vue3中的一个新的函数,Effect翻译过来有反应的意思,所以又叫监视反应
它也可以做监视,但是它最大的特点就是不说监视谁,直接写回调
下面通过案例演示下watchEffect的简单使用
引用watchEffect
和其他函数一样,在vue3中想要使用,就必须先定义
// 引入watchEffect函数 import watchEffect} from "vue";
编写watchEffect
它最大的特点就是不说监视谁,直接写回调,而且没有newValue和oldValue这两个参数
可以发现一上来就执行了,但是谁也每被监视
watchEffect很智能,它会通过函数体发现用到了哪个属性,用到了哪个属性才会监视哪个属性,并且会自动解析多级对象
这样就能帮助我们写一些复杂的逻辑,只要监测到改变哪个属性,就能执行哪些逻辑或者函数,可以提高我们的开发效率
watchEffect函数总结
- watch的套路是:既要指明监视的属性,也要指明监视的回调。
- watchEffect的套路是:不用指明监视哪个属性,监视的回调中用到哪个属性,那就监视哪个属性。
- watchEffect有点像computed:
- 但computed注重的计算出来的值(回调函数的返回值),所以必须要写返回值。
- 而watchEffect更注重的是过程(回调函数的函数体),所以不用写返回值。
//watchEffect所指定的回调中用到的数据只要发生变化,则直接重新执行回调。 watchEffect(()=>{ const x1 = sum.value const x2 = person.age console.log('watchEffect配置的回调执行了') })
9 vue3生命周期
- Vue3.0中可以继续使用Vue2.x中的生命周期钩子,但有有两个被更名:
beforeDestroy
改名为beforeUnmount
destroyed
改名为unmounted
- Vue3.0也提供了 Composition API 形式的生命周期钩子,与Vue2.x中钩子对应关系如下:
beforeCreate
===>setup()
created
=======>setup()
beforeMount
===>onBeforeMount
mounted
=======>onMounted
beforeUpdate
===>onBeforeUpdate
updated
=======>onUpdated
beforeUnmount
==>onBeforeUnmount
unmounted
=====>onUnmounted
10 自定义hook函数
hook(钩住)本质上是一个函数,把setup函数中使用的Composition API进行封装成自定义的公共逻辑,如果其他组件想要使用这个逻辑直接引入hook即可,类似vue2中的mixin(混入)
hook把组合式API体现的淋漓尽致,下面通过案例演示一下hook的使用,一起感受下hook函数的强大以及什么叫组合式API。
编写案例
现在想编写一个鼠标点击页面,展示对应x,y之的功能。
虽然效果实现了,但是完全如果哪天某一个组件也想要这个功能呢,难道要重新写一遍吗,这个时候就体现出来hook的作用了,我们完全可以把这些逻辑写在hook中,哪些组件想要使用这些逻辑,直接引入这个hook文件就行
编写hook文件
一般编写hook文件,都会在src里面定义一个hooks文件,里面专门存放hook函数,这样文件夹就命名好了
关于文件名,理论来说是可以随便命名,但是为了规范,hook文件有一个特点:一般会以usrxxx开头,像我们这个hook逻辑,就是针对坐标点进行操作的,所以就可以命名为usePoint。
然后就把公共的逻辑放到一个函数里面返回即可,需要什么就在hook中引入即可
这样一个简单的hook函数自定义逻辑就完成了,我们只需要引入它使用即可!
注意不要忘了把这个hook文件进行暴露!!!
引入hook文件并使用
直接引入hook文件,因为hook本质就是一个函数,并且有返回值,所以我们可以直接调用接收返回值
我们只关心hook的返回结果,并不关心hook里面的逻辑
效果也是一样的
这个时候如果有一个组件也想实现这个功能,那也是一样的引入hook即可
自定义hook函数总结
- 什么是hook?—— 本质是一个函数,把setup函数中使用的Composition API进行了封装。
- 类似于vue2.x中的mixin。
- 自定义hook的优势: 复用代码, 让setup中的逻辑更清楚易懂。
11 toRef API和toRefs API
toRef也是一个很强大并且常用的API,它用于创建一个 ref 对象,并且value值指向另一个对象中的某个属性
下面通过案例演示下toRef API的使用
编写案例
现在有个问题,我返回的是一个person对象,所以用到里面的属性都要通过person.的方式来获取,那么能不能优化一下呢?
当然是可以的,我们可以通过toRef去优化
引入并使用toRef API
和其他函数一样,使用之前需要引入
// 引入toRef API import {toRef} from "vue";
它有两个参数:参数1:对象,参数2:对象里面的属性
也是可以正常修改数据的
但是这样频繁的写toRef也不好,所以就有了toRefs这个API
引入并使用toRefs API
toRefs是toRef的升级版,它可以一次性处理多个对象属性
和toRef一样,第一步也要先引入
// 引入toRefs API import {toRefs} from "vue";
引入完成后就可以使用了
但是toRefs只能获取第一层对象,所以处理多层对象的时候,还得自己手动嵌套处理
toRef总结
- 作用:创建一个 ref 对象,其value值指向另一个对象中的某个属性。
- 语法:
const name = toRef(person,'name')
- 应用: 要将响应式对象中的某个属性单独提供给外部使用时。
- 扩展:
toRefs
与toRef
功能一致,但可以批量创建多个 ref 对象,语法:toRefs(person)