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
        },

 

posted @ 2021-11-08 13:16  keyeking  阅读(2605)  评论(0编辑  收藏  举报