什么是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
},

