Composition API
什么是Composition API
Composition API是根据逻辑功能进行代码组件,可以把不同的代码放在一起。也可以把他们单独的放在一个函数---基于函数组合的API
为什么要使用Composition API
在Compostion API的这种组织代码方式下可以提高代码的可读性和可维护性
Compostion API可以更好的重用逻辑代码
在vue3中使用Compostion API是可选的
-
setup
setup函数是Composition API的入口函数,我们的变量,方法都是在该函数中定义的
setup是先于beforeCreate和created执行的
由于在执行 setup 时尚未创建组件实例,因此在 setup 选项中没有 this
app.vue
<template> <div> app <mysetup></mysetup> </div> </template> <script> import mysetup from "./components/mysetup.vue" export default { components:{ mysetup } } </script> <style lang="less" scoped> </style>
mysetup.vue
<template> <div> setup </div> </template> <script> export default { setup(){ console.log("setup") console.log(this) //定义变量 方法 return {} //这里返回的任何内容都可以用于组件的其余部分 }, beforeCreate(){ console.log("beforeCreate") }, created(){ console.log("created") } } </script> <style lang="less" scoped> </style>
可以看到setup先执行
注意:在 setup
中应该避免使用 this
,因为它不会找到组件实例。setup
的调用发生在 data
property、computed
property 或 methods
被解析之前,所以它们无法在 setup
中被获取。
我们可以在setup中输出this,查看一下setup中的this指向
我们可以看到浏览器输出为undefined
-
ref
ref函数包装了一个响应式的数据对象,将值传递给对象中的value属性,每次访问都要加 .value
在template模板中访问是不需要加value,因为在编译时,会自动识别是否为ref包装过的
<template> <div> setup </div> </template> <script> import {ref} from "vue" //引入vue中的ref否则会报错 export default { setup(){ let state=ref({count:1}) console.log(state) //定义变量 方法 return {} //这里返回的任何内容都可以用于组件的其余部分 }, } </script> <style lang="less" scoped> </style>
我们可以看到浏览器的输出为:
我们通过.value来获取值
console.log(state.value)
如果要在模板中使用数据的话,得需要return返回出去
<template> <div> {{state}}
{{state.count}}
</div> </template> <script> import {ref} from "vue" //引入vue中的ref否则会报错 export default { setup(){ let state=ref({count:1}) console.log(state.value) //定义变量 方法 return {state} //这里返回的任何内容都可以用于组件的其余部分 }, } </script> <style lang="less" scoped> </style>
此时我们return出去之后,就可以通过胡子语法使用了
打开浏览器可以看到输出了结果值
-
reactive
reactive函数用来创建一个响应式的数据对象,很好的解决了vue2通过defineProperty实现数据响应式的缺陷
用法比较简单,只需要把数据作为参数传入就可以
<template> <div> {{state}} {{state.count}} {{name}} {{name.name}} </div> </template> <script> import {ref,reactive} from "vue" //引入vue中的ref否则会报错 export default { setup(){ let state=ref({count:1}) console.log(state.value) let name= reactive({name:'小明'}) console.log(name) return {state,name} //这里返回的任何内容都可以用于组件的其余部分 }, } </script> <style lang="less" scoped> </style>
reactive获取数据的时候不要添加.value
如何选择ref和reactive
1:基本数据类型(string,number,boolean等)或者单值对象({count:1}这样只有一个属性值的对象)使用ref
2:引用类型的值 (Object,Array)使用reactive
-
toRef
toRef是将某个对象中的某个值转化为响应式数据,其接收两个参数,第一个参数是obj数据,第二个参数为对象中的属性名
<template> <div> {{state}} </div> </template> <script> import {toRef,ref} from "vue" export default { setup(){ let obj={count:1} //将obj对象中属性count转换为响应式数据 let state=toRef(obj,'count') console.log(toRef) //将ref包装过的数据对象返回给template使用 return {state} }, } </script> <style lang="less" scoped> </style>
使用ref同样可以实现
setup(){ let obj={count:1} let state=ref(obj.count) return {state} }
-
ref和toRef的区别
ref是对传入数据的拷贝;toRef是对传入数据的引用
ref的值改变会更新视图;toRef的值改变不会更新视图
我们看下面的案例
<template> <div> {{state}}<button @click="add1">增加ref</button> <hr/> {{state2}}<button @click="add2">增加toRef</button> </div> </template> <script> import {toRef,ref} from "vue" export default { setup(){ let obj={count:1} let state=ref(obj.count) let state2=toRef(obj,'count') function add1(){ state.value++ console.log("原始值:",obj) console.log("响应式数据对象",state) } function add2(){ state2.value++ console.log("原始值:",obj) console.log("响应式数据对象",state2) } return {state,state2,add1,add2} }, } </script> <style lang="less" scoped> </style>
此时我们点击一下,原始值没有变化,也就是说在点击按钮的时候,视图反生改变了,但是原始值却没有发生改变,响应式数据对象的值也跟着改变,这说明ref对原始子数据的一个拷贝,不会影响到原始值,同时响应式数据对象值改变后会同步更新视图
此时我们点击toref的按钮可以看到
视图没有发生变化,但是原始数据和响应式数据的值也反生改变,这说明toRef是对原始数据的一个引用,会影响到原始值,但先影视数据改变之后是不会更新试图的
-
toRefs
toRefs是将传入的对象里所有的属性的值转化为响应式数据对象,该函数支持一个参数,即obj对象
<template> <div> {{state}} {{name}}-{{age}}-{{scored}} </div> </template> <script> import {toRefs} from "vue" export default { setup(){ const obj={ name:"小明", age:20, scored:100 } //将obj对象的所有属性转换为响应书数据 const state=toRefs(obj) //返回一个对象,对象里包含了每一个包装过的响应式数据对象 console.log(state) return {...state} } } </script> <style lang="less" scoped> </style>
-
watch和watcheffect
watch和watchEffect都是用来监视某项数据变化从而执行指定的操作的,两者的用法是有所区别
watch(source,callback,[options])
source:可以是表达式或函数,用于指定监听的依赖对象
callback:依赖对象变化后执行的回调函数
options:可选参数,可以配置的属性由 immediate(立即出发回调函数),deep(深度监听)
watch监听ref数据
<template> <div> </div> </template> <script> import {ref,watch} from "vue" export default { setup(){ let state=ref(0); watch(state,(newdata,olddata)=>{ console.log("原始值",olddata); console.log("新值",newdata); //1秒后state的值增1 }) setTimeout(()=>{ state.value++ },1000) } } </script> <style lang="less" scoped> </style>
一秒后打印的结果为:
watch监听reactive数据
1秒后执行结果为
同时监听多个值
<script> import {watch,reactive} from "vue" export default { setup(){ let state=reactive({count:0,name:'小明'}) watch( [()=>state.count,()=>state.name], ([newcount,newname],[oldcount,oldname])=>{ console.log(`新值newcount${newcount}`); console.log(`新值newname${newname}`); console.log(`原始值oldcount${oldcount}`); console.log(`原始值oldname${oldname}`); }) setTimeout(()=>{ state.count++ state.name='小红' },1000) } } </script>
如果我们想让初始化组件时先执行一次第二个函数对应的回调函数,可以在第三个参数对象中设置immediate:true
<script> import {watch,ref} from "vue" export default { setup(){ const state=ref(0) watch(state,(newdata,olddata)=>{ console.log("原始值",olddata); console.log("新值",newdata); },{immediate:true}) setTimeout(()=>{ state.value++ },3000) } } </script>
此时刷新页面的时候就会执行一次
若要监听多层嵌套的数据,在第三个参数中设置 deep:true
setup(){ const state=reactive({obj:{name:"小明"}}) watch(()=>state.obj.name,(newdata,olddata)=>{ console.log(`新值newdata${newdata}`); console.log(`原始值olddata${olddata}`); },{deep:true}) setTimeout(()=>{ state.obj.name="小红" },1000) }
watch方法会返回一个stop方法,若想要停止监听,便可以执行该stop函数
<template> <div> <button @click="stop">停止监听</button> </div> </template> <script> import {watch,reactive} from "vue" export default { setup(){ const state=reactive({obj:{name:"小明"}}) let stop=watch(()=>state.obj.name,(newdata,olddata)=>{ console.log(`新值newdata${newdata}`); console.log(`原始值olddata${olddata}`); },{deep:true}) console.log(stop); setTimeout(()=>{ state.obj.name="小红" },1000) return {stop} } } </script> <style lang="less" scoped> </style>
我们可以看到控制台输出的stop函数
此时我们点击一下这个按钮,便停止了监听,控制台也不会输出
-
watchEffect
1>不需要手动传入依赖
2>每次初始化时会执行一次回调函数来自动获取依赖
3>无法获取到原值,只能得到变化后的值
<script> import {watchEffect,reactive} from "vue" export default { setup(){ let state=reactive({count:0,name:"小明"}) watchEffect(()=>{ //这里不需要添加依赖 console.log(state.count); console.log(state.name); }) setTimeout(() => { state.count++ state.name="小红" }, 1000); } } </script>
组件初始化时,将该回调函数执行一次,自动获取到需要检测的数据是state.count和state.name
-
computed
我们需要的某些属性依赖另一些属性,在vue2中,可以使用computed属性实现,在vue3中写法有些不同,需要导出computed(),不过同样也支持get()和set(),支持修改计算状态
1>传入一个getter函数,返回一个默认不可修改的ref对象
<template> <div> {{count}} <button @click="count++">更改count值</button> {{newdata}} </div> </template> <script> import {ref,computed} from "vue" export default { setup(){ let count=ref(1) //传入一个getter函数 //返回一个不可手动修改的ref对象,需要加.value来访问属性 let newdata=computed(()=>count.value+2) console.log(newdata.value); //如果要把这个计算属性要在页面上展示,需要把它return出去,同事也要把定义的countreturn出去 return {newdata,count} } } </script> <style lang="less" scoped> </style>
我们可以看到newdata中的值始终比count的值多2
注意:computed返回一个不可手动修改的ref对象
如果我们在这里修改newdata中的值就会报错,提示你这个是只读的属性
newdata.value++
2>传入一个有set和get函数的对象,创建一个可手动修改的计算属性
<template> <div> {{count}} <button @click="count++">更改值</button> {{com}} <input type="text" v-model.number="com">设置值 </div> </template> <script> import {ref,computed} from "vue" export default { setup(){ let count=ref(0) let com = computed({ get:()=>{ //获取值的时候执行 console.log("获取值"); return count.value+1 }, set:()=>{ //设置值的时候执行 console.log("设置值"); count.value-1 } }) return {count,com} } } </script> <style lang="less" scoped> </style>
生命周期函数
vue2 和vue3 生命周期对比
vue2 |
vue3 |
含义 |
beforeCreate | setup | |
created | setup | |
beforeMount | onBeforeMount | 组件挂载到页面之前执行 |
mounted | onMounted |
组件挂载到页面之后执行 |
beforeUpdate | onBeforeUpdate | 组件更新之前执行 |
updated | onUpdated | 组件更新之后执行 |
beforeDestroy | onBeforeUnmount | 组件卸载之前执行 |
destroyed | onUnmounted | 组件卸载之后执行 |
activated | onActivated | 写在keep-alive钩子函数 |
deactivated | onDeactivated | |
errorCaptured | onErrorCaptured | 捕获子组件异常函数 |
setup函数是在beforeCreate和created之前执行的,所以使用setup来代替两个钩子函数
vue3 还增加了onRenderTracked和onRenderTriggered函数
<script> import {ref,computed, onBeforeMount, onMounted, onBeforeUpdate, onUpdated} from "vue" export default { setup(){ console.log("开始创建组件---setup") onBeforeMount(()=>{console.log('组件挂载到页面之前执行---onBeforeMount')}) onMounted(()=>{console.log("组件挂载到页面之后执行---onMounted")}) onBeforeUpdate(()=>{console.log("组件更新之前执行---onBeforeUpdate")}) onUpdated(()=>{console.log("组件更新之后执行---onUpdated");}) return {} } } </script>
此时我们可以看到
此时我们添加数据
<template> <div> {{count}} <button @click="count++">更改数据</button> </div> </template> <script> import {ref,computed, onBeforeMount, onMounted, onBeforeUpdate, onUpdated} from "vue" export default { setup(){ let count=ref(0) console.log("开始创建组件---setup") onBeforeMount(()=>{console.log('组件挂载到页面之前执行---onBeforeMount')}) onMounted(()=>{console.log("组件挂载到页面之后执行---onMounted")}) onBeforeUpdate(()=>{console.log("组件更新之前执行---onBeforeUpdate")}) onUpdated(()=>{console.log("组件更新之后执行---onUpdated");}) return {count} } } </script> <style lang="less" scoped> </style>
可以看到:
当我们点击之后
可以在setup之后编写vue2的生命周期函数
<template> <div> {{count}} <button @click="count++">更改数据</button> </div> </template> <script> import {ref,computed, onBeforeMount, onMounted, onBeforeUpdate, onUpdated} from "vue" export default { setup(){ let count=ref(0) console.log("vue3---开始创建组件---setup") onBeforeMount(()=>{console.log('vue3---组件挂载到页面之前执行---onBeforeMount')}) onMounted(()=>{console.log("vue3---组件挂载到页面之后执行---onMounted")}) onBeforeUpdate(()=>{console.log("vue3---组件更新之前执行---onBeforeUpdate")}) onUpdated(()=>{console.log("vue3---组件更新之后执行---onUpdated");}) return {count} }, beforeCreate(){ console.log("vue2的---beforeCreate"); }, created(){ console.log("vue2的---created"); }, beforeMount(){ console.log("vue2的---beforeMount"); }, mounted(){ console.log("vue2的---mounted"); }, beforeUpdate(){ console.log("vue2的---beforeUpdate"); }, updated(){ console.log("vue2的---updated"); } } </script> <style lang="less" scoped> </style>
此时我们可以看到
点击一下之后
-
setup参数
setup函数,它会接收两个参数:
1>props:接收父组件向子组件传递的数据
2>context 上下文对象
props
setup函数的第一个参数props是响应式的,当传入新的props时,它将被更新
注意:props是响应式的,不能使用es6的解构,就会消失props的响应性
我们在父组件中设置一个值
<template> <div> app <watch :msg=msg></watch> </div> </template> <script> import {ref} from "vue" import watch from "./components/watch.vue" export default { components:{ watch }, setup(){ let msg=ref("helo") return {msg} } } </script> <style lang="less" scoped> </style>
在子组件中接收这个值
<template> <div> </div> </template> <script> export default { // props中定义当前组件,允许外界传递过来的参数名称 props:{ msg:String, }, setup(props){ console.log(props) } } </script> <style lang="less" scoped> </style>
我们可以看到浏览器输出为:
此时我们在父组件中修改这个值
<template> <div> app <watch :msg=msg></watch> <button @click="msg='word'">更改msg</button> </div> </template> <script> import {ref} from "vue" import watch from "./components/watch.vue" export default { components:{ watch }, setup(){ let msg=ref("helo") return {msg} } } </script> <style lang="less" scoped> </style>
子组件
<template> <div> {{ msg}} </div> </template> <script> export default { // props中定义当前组件,允许外界传递过来的参数名称 props:{ msg:String, }, setup(props){ console.log(props) } } </script> <style lang="less" scoped> </style>
我们点击之后可以看到
此时的msg值已经修改
此时我们如果要传入多个参数
注意:不能使用es6的解构,因为props是响应式的,使用es6的解构,就会消失props的响应性
<template> <div> {{ msg}} </div> </template> <script> export default { // props中定义当前组件,允许外界传递过来的参数名称 props:{ msg:String, }, setup(props){ console.log(props) let {msg,size,num}=props console.log({msg,size,num}) return {msg,size,num} } } </script> <style lang="less" scoped> </style>
若传入多个参数需要解构,通过toRefs完成
setup(props){ console.log(props) let {msg}=toRefs(props) console.log(msg.value) return {} }
此时就可以解构成功并输出
此时我们以同样的方法输出size和num就会报错
console.log(size.value) console.log(num.value)
是因为没有在props定义传来的参数
// props中定义当前组件,允许外界传递过来的参数名称 props:{ msg:String, },
我们在这里定义一下
// props中定义当前组件,允许外界传递过来的参数名称 props:{ msg:String, size:String, num:String },
此时就可以输出了
context 上下文对象
这个上下文对象包含了一些有用的属性,这些属性在vue2中需要通过this才能访问到,在vue3,访问如下:
我们可以看一下context是什么
export default { setup(props,context){ console.log(props,'props'); console.log(context,'context'); console.log(context.attrs,'context.sttrs'); //非响应式的对象 console.log(context.slots,'context.slots'); //非响应式的对象 console.log(context.emit,'context.emit'); //触发事件 } }
context.emit和vm.$emit可以触发实例上的监听事件
app.vue
<template> <div> app <watch :msg=msg size="10" num="20" @abc="print"></watch> <button @click="msg='word'">更改msg</button> </div> </template> <script> import {ref} from "vue" import watch from "./components/watch.vue" export default { components:{ watch }, setup(){ let msg=ref("hello") function print(v){ console.log("print",v) } return {msg,print} } } </script> <style lang="less" scoped> </style>
watch.vue
<template>
<div>
{{ msg}}
</div>
</template>
<script>
import { toRefs } from '@vue/reactivity'
export default {
setup(props,context){
console.log(props,'props');
console.log(context,'context');
console.log(context.attrs,'context.sttrs'); //非响应式的对象
console.log(context.slots,'context.slots'); //非响应式的对象
console.log(context.emit,'context.emit'); //触发事件
//触发实力上的监听事件
context.emit("abc","emit监听事件")
}
}
</script>
<style lang="less" scoped>
</style>
此时就会输出这个事件
context.slots的使用
watch.vue
<template>
<div>
{{ msg}}
<slot name="content"></slot>
</div>
</template>
<script>
import { toRefs } from '@vue/reactivity'
export default {
setup(props,context){
console.log(context.slots,'context.slots'); //{content:f}
}
}
</script>
<style lang="less" scoped>
</style>
app.vue
<template>
<div>
app
<watch :msg=msg size="10" num="20" @abc="print"></watch>
<button @click="msg='word'">更改msg</button>
<watch>
<template v-slot:content>
<p>app.vue</p>
</template>
</watch>
</div>
</template>
<script>
import {ref} from "vue"
import watch from "./components/watch.vue"
export default {
components:{
watch
},
setup(){
let msg=ref("hello")
function print(v){
console.log("print",v)
}
return {msg,print}
}
}
</script>
<style lang="less" scoped>
</style>
注意:只能访问具名插槽,没有命名的插槽v-slot:default的没有暴露的
attrs和props的区别
- props要在当前组件props属性里声明才可以取值,attrs不用先声明
- props不包含事件,attrs包含
- props没有声明的属性,会跑到attrs里
- 当我们在html标签里只写属性不赋值的时候,props支持string以外的类型,attrs只用string类型
我们看一下attrs和props输出的内容
我们可以看到attrs直接把传入的属性输出了,而props什么也没有输出
要使用props需要在props属性中定义
<script> import { toRefs } from '@vue/reactivity' export default { props:{ msg:String, size:String, num:String }, setup(props,context){ console.log(props); console.log(context.attrs,'context.attrs'); //{content:f} } } </script>
此时我们呢就可以看到props输出了这些属性,但是attrs没有了这些属性
这是因为props没有声明的属性,会跑到attrs里,如果props中声明了就不会跑到attrs中
在props中没法定义事件,
所以abc事件在attrs中不在props中
我们在app.vue中定义一个属性
<watch :msg=msg size="10" num="20" @abc="print" disa></watch>
可以看到输出为字符串类型
如果我们在watch.vue中的props中定义disa为布尔类型,我们可以看到他的值为true
props:{ msg:String, size:String, num:String, disa:Boolean },