vue学习笔记之组合式API

2023-12-13 04:11:14

第九章 组合式API

9.1 认识组合式API (Composition API)

  • 为了实现基于函数的逻辑复用机制而产生
  • 提供一组函数来组织和管理组件的逻辑代码,将相关功能按照逻辑关系进行分组,使得代码更加清晰、可维护,并且能够更方便地实现逻辑的复用。
  • 主要思想:定义为从setup函数返回的JavaScript变量,不再将组件的功能(state、methods、computed等)定义为对象属性
  • 使用:
    • setup函数:用来初始化组合式API。它接收两个参数,propscontext,并返回一个对象,包含了组件中需要的响应式数据、计算属性、方法等
    • 组合函数:在setup函数里自定义组合函数用于封装和复用一些逻辑。例如,使用refcomputedwatch等函数创建响应式数据,使用onMountedonUpdated等函数注册生命周期钩子等
    • 使用组合函数:在模板和其它组合函数中,可以直接使用定义的组合函数,并将其返回的值用于渲染视图或执行其他操作。

9.2 setup()方法应用

  • 定义setup函数

    <script>
        setup(){
            let num = 0;
        }
    </script>
    

    使用语法糖

    <script setup>
        let num  = 0;
        
        function add(){
            num++;
            console.log(num)
        }
    </script>
    
  • 执行时机

    • beforeCreate之前(组件实例还没被创建)就执行

    • 此时thisundefined,无法通过它来访问data/computed/methods等

      export default{
        setup(){
          //调用时机,在没有创建该vue实例前就被调用
          console.log('setup方法被调用')
          console.log(this)//结果是undefined
          //setup中无法直接使用this.$router $store $refs $emit
        },
        //允许共存,但setup优先级更高
        data(){
        }
      
  • 返回值

    • 不使用语法糖,需要在模板中使用的变量和方法,setup必须返回

    • setup返回的属性和方法会和datamethods中的成员合并,重名setup优先

      <script>
      export default{
        setup(){
          //调用时机,在没有创建该vue实例前就被调用
          console.log('setup方法被调用')
          console.log(this)//结果是undefined
          //setup中无法直接使用this.$router $store $refs $emit
      
          let a=10;
          const add = ()=>{
            console.log(a);
          }
          //模板中需要使用的变量和方法必须以对象(json格式)返回
          return{
            val:a,
            fun1:add,
            //可以直接在这里声明
            fun2(){
              console.log(this.val);
            }
          }
        }
      }
      </script>
      
      <template>
        <div>
          {{val}}{{fun1()}}{{fun2()}}
        </div>
      </template>
      
  • setup()的参数:

    • 第一个参数是props

      父组件

      <script>
      import SubComp from "@/views/SubComp.vue";
      export default{
        components:{
          SubComp,
        },
        setup(){
         
          let a=10;
          //模板中需要使用的变量和方法必须以对象(json格式)返回
          return{
            val:a,
          }
      
        },
      
      }
      
      </script>
      
      <template>
        <div>
          <hr>
          <!--这里变量应该是setup返回的变量名-->
          <sub-comp :one="val" :str="111"></sub-comp>
        </div>
      </template>
      

      子组件

      <script>
        export default{
          name:"SubComp",
          props:{
            one:{
              type:Number,
            },
            str:{
              type:String,
            }
          },
          //第一个参数,接收父组件的属性
          setup(props){
            console.log(props);
          }
      
        }
      </script>
      
      <template>
        <div>
          {{one}}
          <br>
          {{str}}
        </div>
      </template>
      
    • 第二个参数是 上下文context/ {attrs,slots,emit}

      • attrs包含没有在props配置中声明的属性的对象

      • slots包含所有传入的插槽内容的对象

      • **emit**用来分发自定义事件的函数,子组件向父组件传值

        父组件

        <script>
        import SubComp from "@/views/SubComp.vue";
        export default{
          components:{
            SubComp,
          },
          setup(){
           
            let a=10;
             function fun(str){
              console.log(str)
            }  
            //模板中需要使用的变量和方法必须以对象(json格式)返回
            return{
              val:a,
              fun,
            }
        
          },
        
        }
        
        </script>
        
        <template>
          <div>
            <hr>
            <!--这里变量应该是setup返回的变量名-->
            <sub-comp @greet="fun" :one="val" :str="111"></sub-comp>
          </div>
        </template>
        

        子组件

        <script>
          export default{
            name:"SubComp",
            props:{
              one:{
                type:Number,
              },
              str:{
                type:String,
              }
            },
            //第二个参数是context,可以直接解构
            setup(props,{emit,attrs}){
              console.log(props);
              console.log(emit);
              console.log(attrs.data);
        
              const fun = str=>{
                emit('greet',str);
              }
        
              return {
                fun,
              }
          }
        </script>
        
        <template>
          <div>
            <button @click="fun('hello')">greet</button>
          </div>
        </template>
        

9.3 Composition常见API

9.3.1 响应式API

实现原理

  • vue2响应式
    • 对象:通过定义property对对象的已有属性值读取和修改进行劫持(监视/拦截)
    • 数组:通过重写数组更新数组一系列更新元素的方法是实现元素修改的劫持
  • vue3的响应式
    • 内部基于ES6的Proxy实现,通过代理,对象操作源对象内部数据都是响应式的
    • 通过Proxy(代理):拦截对data任意属性的任意(13种)操作,包括属性值的读写,属性的添加,属性的删除等
    • 通过reflect(反射):动态对被代理对象的相应属性进行特定操作

方法

  • ref()

    ref()可以创建一个响应式的数据引用,函数返回一个 RefImpl对象,是Vue内部定义的一个类,它包装了原始值和一些方法

    常见的方法和属性有:valuegetValuesetValue(value)unref()isRef

    <script>
      //API需要引入才能使用,API实际就是方法
      import {ref} from 'vue';
      export default{
        name: 'SetUpApi',
    
        setup(){
          let num = ref(0);
    
          const add2 = ()=>{
            num.value+=2;
          }
    
          return{
            num,
            add2,
          }
        }
      }
    </script>
    
    <template>
      <div>
        <hr>
    <!--    没有响应式改变num-->
    <!--    使用ref声明num后,可以响应式-->
    <!--    在模板中不需要加.value-->
        {{num}}
        <button @click="num++">++</button>
        <button @click="add2">+2</button>
    
      </div>
    
    </template>
    
  • reative()

    reactive()创建一个响应式的数据对象,可以访问对象中的属性。

    函数接收参数为普通js对象,返回一个代理对象Proxy,这个代理对象会拦截对于原始对象属性的访问和修改操作,并通过触发依赖追踪来实现响应性。

  • toRefs()

    与前者搭配,可以解构响应式对象

    <script>
      //API需要引入才能使用,API实际就是方法
      import {ref,reactive,toRefs} from 'vue';
      export default{
        name: 'SetUpApi',
    
        setup(){
          let data1 = reactive({
            num1:0,
            msg1:'this is a str',
            arr1:["aaa","bbb","ccc"],
          })
    
          let data2 = reactive({
            num2:0,
            msg2:'this is a str',
            arr2:["aaa","bbb","ccc"],
          })
          //是一个对象,不需要value也可以直接访问
          console.log(data1.msg1)
    
          return{
            data1,
            // 将data2的属性展开,可以直接访问
            //...data2,
            //保留响应式的展开
            ...toRefs(data2),
          }
        }
      }
    </script>
    
    <template>
      <div>
        <hr>
    <!--    使用reactive声明的变量是一个对象-->
        {{data1.msg1}}{{data1.num1}}
        <button @click="data1.num1++">++</button>
        <br>
    <!--    展开后可以直接访问,但绕过了reactive,失去了响应式 toRefs可以保留响应式地展开-->
        {{msg2}}{{num2}}
        <button @click="num2++">++</button>
      </div>
    
    </template>
    
9.3.2 计算属性API
  • computed()创建计算属性,返回值是一个ref的实例

    computed()函数接收一个计算函数作为参数,该函数用于定义计算属性的逻辑。

    计算函数会在相关的响应式属性发生改变时自动重新计算,并缓存计算结果,只有在依赖的响应式属性发生改变时才会重新执行计算函数

    计算属性只能用于获取数据,不能用于修改数据。如果需要进行双向绑定或修改计算属性的值,可以考虑使用带有**getset函数**的计算属性

    <script>
      //API需要引入才能使用,API实际就是方法
      import {ref,reactive,toRefs,computed} from 'vue';
      export default{
        name: 'SetUpApi',
    
        setup(){
    
          let data1 = reactive({
            num1:0,
            msg1:'this is a str',
            arr1:["aaa","bbb","ccc"],
            //计算属性可以写进响应式数据,使用set才能修改,这种使用比较少,但需要改变则一般写进这里
            sjoin:computed({
              get:()=>{
                return data1.num1;
              },
              set:v=>{
                data1.num1=v;
              }
            })
          })
    
          //直接返回默认的是get方法
          const arrjoin = computed(()=>{
            return data1.arr1[0]+data1.arr1[1]+data1.arr1[2];
          })
    
          return{
            data1,
            arrjoin,
            add2,
          }
        }
      }
    </script>
    
    <template>
      <div>
        <hr>
    
    <!--    使用计算属性,这样是非响应式,双向绑定也不能改变-->
        {{arrjoin}}
        <input type="text" v-model="arrjoin"><br>
    <!--    双向绑定,响应式计算属性,可以改变属性值-->
        {{data1.sjoin}}
        <input type="text" v-model="data1.sjoin">
      </div>
    
    </template>
    
9.3.3 侦听器watch
  • watch()函数用于监视某些数据项的变化,从而触发某些特定的操作

  • watchEffect立即执行传入的一个函数,并响应式追踪其依赖,并在其依赖变更时重新运行该函数

    <script>
      //API需要引入才能使用,API实际就是方法
      import {ref,reactive,toRefs,computed,watch,watchEffect} from 'vue';
      export default{
        name: 'SetUpApi',
    
        setup(){
          let num = ref(0);
          let s = ref(0);
    
          let data2 = reactive({
            num2:0,
            msg2:'this is a str',
            arr2:["aaa","bbb","ccc"],
          })
    
          const add2 = ()=>{
            num.value+=2;
          }
    
          watch(()=>{
            //num.value发生改变就会执行该操作,加上第二个参数也可以做到首先执行一次
            console.log(num.value+'watch')
          },{immediate:true});
    
          //第一个参数是监听的对象,第二个参数是一个回调函数,函数可以有参数,第一个是监听值的新值,第二个是监听值的旧值,监听多个则采用数组
          watch([num,s],([numnewv,snewv],[numoldv,soldv])=>{
            console.log(numnewv+"--"+num.value+"--"+numoldv);
            console.log(snewv+"--"+s.value+"--"+soldv);
          })
    
          //监听reactive定义的对象,要保证到底监听对象中的哪一个属性,使用回调函数
          watch(()=>data2.num2,(newv,oldv)=>{
            console.log(newv+"--"+oldv)
          })
    
          //区别在于没有变化也会首先执行一次
          watchEffect(()=>{
            //num.value发生改变就会执行该操作
            console.log(num.value+'watch')
          });
    
          return{
            num,
            ...toRefs(data2),
            add2,
          }
        }
      }
    </script>
    
    <template>
      <div>
        <hr>
    <!--    没有响应式改变num-->
    <!--    使用ref声明num后,可以响应式-->
    <!--    在模板中不需要加.value-->
        {{num}}
        <button @click="num++">++</button>
        <button @click="add2">+2</button>
        <br>
    <!--    展开后可以直接访问,但绕过了reactive,失去了响应式,使用toRefs保留响应式-->
        {{msg2}}{{num2}}
        <button @click="num2++">++</button>
        <br>
      </div>
    
    </template>
    

9.4 组合式API中生命周期函数

  • Vue 3的组合式API中,生命周期函数被替换为一组新的函数,以更灵活和可组合的方式来管理组件的生命周期

  • Composition和原生命周期函数之间的映射关系(对应Vue2.x)

    • setup()beforeCreatecreated
    • onBeforeMountbeforeMount
    • onBeforeUpdatebeforeUpdate
    • onUpdatedupdated
    • onBeforeUnmontedbeforeUnmount
    • onErrorCapturederrorCaptured
  • 新的生命周期函数按需导入到组件中,只能在setup()函数中使用,不能和原来的生命周期函数混用

    <script>
    import {ref,toRefs,reactive,computed,onMounted,onBeforeMount} from "vue";
    
    export default{
      name:"two",
    
      setup(){
        onMounted(()=>{
          console.log('222');
        })
    
        //setup中的最早执行
        console.log('111')
      },
    
      //混用则只会在setup后执行
      mounted() {
        console.log('###')
      }
    
    }
    </script>
    
    111
    222
    ###
    

9.5 组合API中provide和inject使用

  • 组件间数据的传递

    • 父子组件:通过props,$emit,【$root,$parent,$children
    • 非父子组件:通过Vuex实现数据的共享、父子组件层层传递、$ref
    • vue3引入 provideinject用于跨组件传递数据,允许在组件树中的任何地方直接注入和获取数据,隔代传递,无需通过组件层级传递。使用频率较少,前两种情况已经包括大多数
  • provide/inject这对选项允许一个祖先组件向其所有子孙后代注入一个依赖,无论组件层次有多深,在上下游关系成立的时间里始终生效

    • provie相当于加强版父组件prop,提供变量:Object|()=>Object
    • inject相当于加强版子组件的props,注入变量:Array<string>|{[key:string]:string |Object}
  • 实例

    grandparents

    <script>
    import fu from "../components/fu.vue";
    import {provide,ref,toRefs,reactive,computed,onMounted,onBeforeMount} from "vue";
    export default{
      name:"two",
      components:{
        fu,
      },
    
      //vue2使用方法
      provide:{
        title:"数据传递",
        num:1,
      },
    
      setup(){
        //vue3使用
        provide('title2','hello world')
        provide('num2',0)
    }
    </script>
    
    <template>
      <div>
        <h1>grandparents</h1>
        <fu></fu>
      </div>
    </template>
    

    parents

    <script>
    import sun from "./sun.vue"
    export default {
      name:'fu',
      components:{
        sun,
      },
    }
    </script>
    
    <template>
      <div>
        <h2>parents</h2>
        <sun></sun>
      </div>
    </template>
    

    grandson

    export default {
      name:'sun',
      //vue2使用方式
      inject:['title','num'],
      setup(){
    
        let title2 = inject('title2');
        let num2 = inject('num2');
    
        return{
          title2,
          num2
        }
    
      }
    }
    </script>
    
    <template>
      <div>
        <h3>grandson</h3>
        {{title}}<br>
        {{num}}<br>
        {{title2}}<br>
        {{num2}}
      </div>
    </template>
    

9.6 组合式API结合路由

  • setup()中利用Router,使用 useRouter()useRoute()等效于 this.$useRouterthis.$useRoute 模板可以直接访问,不需要返回

  • 为特定的路由定义导航守卫:

    • onBeforeRouteLeave((to,from)=>{//离开当前路由前执行的逻辑})
    • onBeforeRouteUpdate(async(to,from)=>{//当前路由重新渲染或参数发生变化前执行的逻辑})
    <script>
    import {useRoute,useRouter} from 'vue-router'
    import {useStore} from "vuex";
    
    export default {
      name:'Three',
      setup(){
        //this.$route
        const route = useRoute();
        //this.$router
        const router = useRouter();
      
        console.log(route.query.page);
    
        const go = path=>{
          router.push({path,query:{name:'ddd',age:21}});
        }
    
        return{
          go,
        }
      }
    }
    
    </script>
    
    <template>
      <div>
        <button @click="go('/two')">go_two</button>
      </div>
    
    </template>
    

9.7 组合式API结合Vuex

  • setup()中使用store,可以调用useStore()函数,等效this.$store, 模板可以直接访问,不需要返回

    import {useStore} from 'vuex'
    const store = useStore()
    
  • 为了访问状态和获取方法,需要创建computed引用以保留响应式

  • 直接调用 store.statestore.

    <script>
    import {useRoute,useRouter} from 'vue-router'
    import {useStore} from "vuex";
    
    export default {
      name:'Three',
      setup(){
        
        //this.$store
        const store = useStore();
    	
        //不需要return也能直接访问,外部能直接访问到store
        console.log(store.state.cartnum)
      }
    }
    
    </script>
    
    <template>
      <div>
        {{store.state.cartnum}}
      </div>
    
    </template>
    

9.8 其他API

  • defineProps
  • defineEmits(原来使用emits定义)

文章来源:https://blog.csdn.net/bfbshs_ddd/article/details/134887108
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。