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指向

   setup(){
             console.log("setup")
             console.log(this)
                //定義變量  方法
                return {}   //這里返回的任何內容都可以用於組件的其余部分
        },

我們可以看到瀏覽器輸出為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數據

<template>
    <div>

    </div>
</template>

<script>
import {watch,reactive} from "vue"
    export default {
            setup(){
        const state=reactive({count:0})
        watch(()=>state.count,(newValue,oldValue)=>{
            console.log(`原始值${oldValue}`);
            console.log(`新值${newValue}`);
        })

        setTimeout(()=>{
            state.count++
        },2000)
    }
    }
</script>

<style lang="less" scoped>

</style>

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輸出的內容

<script>
import { toRefs } from '@vue/reactivity'
    export default {
        setup(props,context){
            console.log(props);
            console.log(context.attrs,'context.attrs');  //{content:f}
        }
    }
</script>

 

 我們可以看到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
        },

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM