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