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

