vue3.0 - Composition API

一. 介绍

》使用传统的option配置方法写组件的时候问题,随着业务复杂度越来越高,代码量会不断的加大;由于相关业务的代码需要遵循option的配置写到特定的区域,导致后续维护非常的复杂,同时代码可复用性不高,而composition-api就是为了解决这个问题而生的。

》Composition API字面意思是组合APl,它是为了实现基于函数的逻辑复用机制而产生的。主要思想是,我们将它们定义为从新的 setup函数返回的JavaScript变量,而不是将组件的功能(例如state、method、computed等)定义为对象属性。

体验

1. 首先创建一个vue脚手架, 我们在About组件中进行操作
在export default配置项中,我们不用写传统的vue配置项格式了,而使用setup函数来代替,这里我们创建一个对象,需要写在reactive方法里,reactive方法需要从vue中引入,然后在末尾用return返回

import {reactive} from 'vue'
export default {
  setup(){
    const data = reactive({
      count:0
    })

    return {data}  //返回出去了才能访问到
  }
}

 在页面中访问:

count:{{data.count}}

2. 如果我们使用一个简单的计算属性(这里是一个方法),可以将计算属性返回的结果直接保存至变量中,注意计算属性也是需要引入的

import {reactive, computed} from 'vue'
export default {
  setup(){
    const data = reactive({
      count:0,
      double:computed(()=>data.count*2)
    })
    return {data}
  }
}

count:{{data.count}}

double:{{data.double}}

3. 定义方法,方法也是定义在setup里,通过return返回,因为setup本身就是一个方法,这里就不能简写add方法了,完整代码↓

count:{{data.count}}

double:{{data.double}}

import {reactive, computed} from 'vue'
export default {
  setup(){
    const data = reactive({
      count:0,
      double:computed(()=>data.count*2)
    })
    function add(){
      data.count++;
    }
    return {data,add}
  }

二. 使用详解

1. setup方法应用

》setup()函数是vue3中专门新增的方法,可以理解为Composition Api的入口.

》执行时机在beforecreate之后,create之前执行.

setup函数在创建组件之前被调用,所以在setup被执行时,组件实例并没有被创建。因此在setup函数中,我们将没有办法获取到this

export default {
  name:'SubComp',
  props:{
    one:{
      type:String
    },
    two:{
      type:String
    }
  }
  setup(){
    // 在创建组件之前调用的,没有this
    console.log('setup');
    console.log(this);  //undifined
  },
  beforeCreate() {
    console.log('beforeCreate--');
  },
  created() {
    console.log('Created--');
    console.log(this);   //Proxy {…}
    
  },
}

但是setup函数提供了两个参数:props和context,用来访问其他组件传过来的数据

props:在父组件向子组件传值时,子组件用props属性接收到的数据,可以通过setup中的props参数访问

export default {
  name:'SubComp',
  props:{
    one:{
      type:String
    },
    two:{
      type:String
    }
  },
  setup(props){
    console.log(props.one+props.two);
  }
}

context:上下文对象,大致包含context.attrs、context.slot、context.parent、context.root、context.emit、context.refs这些属性

export default {
  name:'SubComp',
  props:{
    one:{
      type:String
    },
    two:{
      type:String
    }
  },
  setup(props,context){
    console.log(props.one+props.two);
    //当子组件没有用props接收的时候,可以用attrs访问父组件中的数据
    console.log(context.attrs.desc);
    // 获取父组件插槽的内容
    console.log(context.slots.default());
    //向父类传数据
    context.emit('myadd','向父组件传数据')
  }
}

我们也可以把context解构出来方便使用

  setup(props,{attrs,slots,emit}){
    // 在创建组件之前调用的,没有this
    console.log('setup');
    console.log(this);
    console.log(props.one+props.two);
    //当子组件没有用props接收的时候,可以用attrs访问父组件中的数据
    console.log(attrs.desc);
    // 获取父组件插槽的内容
    console.log(slots.default());
    emit('myadd','向父组件传数据')
  },

2. Composition常用的API工具

ref()函数

在setup中声明一个变量num,以及一个函数,在函数中更改num的值,return出去的num的值并没有变化,原因就在于:原生变量的数据是没有响应式的

export default {
  name:'ComApi',
  setup(){
    let num=1;  
    let myfun=()=>{
      num++;
      console.log(num);   //2
    }
    return{
      num,  //1
      myfun
    }
  }
}

如果我们要声明一个数据是响应式的变量,需要用到ref()函数 ↓

ref()函数用来给定的值创建一个响应式的数据对象,ref()的返回值是一个对象,这个对象上只包含一个.value属性.

ref的变量:{{num2}}{{myfun2(55)}}
import {ref} from 'vue'
export default {
  name:'ComApi',
  setup(){
    //let num=1;  
    //let myfun=()=>{
    //  num++;
    //  console.log(num);   //2
    //}

    let num2=ref(22);
    let myfun2=(newvalue)=>{
      console.log(num2.value);  //55
      num2.value=newvalue;
    }

    return{
      //num,  //1
      //myfun,
      num2,  //55
      myfun2
    }
  }
}

注:在setup程序中用的时候需要.value,在模板中直接用变量名即可

reactive()函数:创建响应式对象

如果我们要声明一个对象,就不能用refs了,需要用到reactive函数将对象变成响应式

reactive中的对象:{{user.name}}---{{user.age}}
import {reactive, ref} from 'vue'
export default {
  name:'ComApi',
  setup(){
    //声明对象:reactive将对象变成响应式
    let user=reactive({
      name:'aaa',
      age:18,
      sex:'男'
    })

    return{
      user
    }
  }
}

 此时插值里用user.name调用比较麻烦,那能不能直接用name调用呢,可以考虑把对象展开,一个一个返回,可以使用es6中的三点运算符

toRefs()函数:解构响应式对象

对象展开之后,数据就不是响应式的了,这时可以使用toRefs函数转换成响应式

{{name}}---{{age}}
name:
age:
import {reactive, ref, toRefs} from 'vue'
export default {
  name:'ComApi',
  setup(){
    //声明对象:reactive将对象变成响应式
    let user=reactive({
      name:'aaa',
      age:18,
      sex:'男'
    })

    return{
      ...toRefs(user)
    }
  }
}

将ref响应式数据挂载到reactive中,当把ref()创建出来值直接挂载到reactive()中时,会自动把响应式数据对象的展开为原始的值,不需要通过.value就可以直接访问到

    let num2=ref(2);

    let user=reactive({
      name:'aaa',
      age:18,
      sex:'男',
      num2
    })

    let myfun2=(newvalue)=>{
      user.num2=100;  //响应式
    }

readonly()函数(不常用)

如果要将响应式的数据转换为原始数据,可以使用readonly函数

import { readonly } from 'vue'  //记得引入

let user2=readonly(user)

isRef()函数(不常用)

用来判断变量是原生的还是响应式的,也需要引入

let num3=isRef(num2)?num2.value=44:num2=55   //44

3. Composition的计算属性API

computed()用来创建计算属性,返回值是一个ref的实例。

1. 引入computed,在setup中定义一个响应式user对象,定义计算属性

import {reactive, toRefs, computed} from 'vue'
export default {
  name:'ComputedDemo',
  setup() {
    const user = reactive({
      firstname:'aaa',
      lastname:'bbb'
    })

    let fullname=computed(()=>{
      return user.firstname+'.'+user.lastname
    })

    return{
      ...toRefs(user),
      fullname
    }
  }
}

 2. 定义完之后,直接在插值里面访问对应的变量就可以了,这里firstname和lastname的值更改之后,fullname也会同步变化

    

计算属性

firstname:
lastname:
{{fullname}}

4. Composition的侦听器watch

》watch()函数用来监视某些数据项的变化,从而触发某些特定的操作。

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

变量侦听

定义(默认会初始化一下,每当值变化的时候就会监听一次):

import { ref } from "vue";
import { watch, watchEffect } from 'vue';
export default {
  name: "WatchDemo",
  setup() {
    let a = ref(1);
    let b = ref(2);

    watch(()=>{
      console.log(a.value+'------'+b.value);
    })

    watchEffect(()=>{
      console.log(a.value+'####'+b.value);
    })

    return {
      a,
      b,
    };
  },
};

watch还有更多用法,比如我们可以监听某个值,当其值变化时进行回调,回调函数中可以传入新值和初始值两个参数;监听某个值时不会自动初始化,通过第三个参数immediate的值是true或false来确认是否以当前的初始值执行回调函数

import { ref } from "vue";
import { watch, watchEffect } from 'vue';
export default {
  name: "WatchDemo",
  setup() {
    let a = ref(1);
    let b = ref(2);


    //监听某个值,不会自动初始化,需要加参数
    watch([a,b],([newA,oldA],[newB,oldB])=>{
      console.log(newA+'---------'+oldA);
      console.log(newB+'#########'+oldB);
    },{immediate:true})

    return {
      a,
      b,
    };
  },
};

对象的侦听

import { reactive,toRefs } from "@vue/reactivity";
import { watch } from "@vue/runtime-core";
export default {
  name: "WatchDemo",
  setup() {
    //监听对象
    const user = reactive({
      a:1,
      b:2
    })

    watch([()=>user.a,()=>user.b],([newA,newB],[oldA,oldB])=>{
      // console.log(user.a+'######'+user.b);
      console.log(newA+'---------'+oldA);
      console.log(newB+'#########'+oldB);
    },{immediate:true})

    return {
      ...toRefs(user)
    }
  },
};

5. Composition的生命周期API

在新版的生命周期函数,可以按需导入到组件中,且只能在setup()函数中使用。

import {onMounted} from 'vue'
export default {
  name:'LifeHook',
  setup() {
    onMounted(() => {
      console.log('mounted!')
    })
    onUpdated(() => {
      console.log('updated!')
    })
    onUnmounted(() => {
      console.log('unmounted!')
    })
  }
}

下面是vue 2.x 的生命周期函数与新版 Composition API 之间的映射关系

// beforeCreate -> use setup()
// created -> use setup()
beforeMount -> onBeforeMount
mounted -> onMounted
beforeUpdate -> onBeforeUpdate
updated -> onUpdated
beforeDestroy -> onBeforeUnmount
destroyed -> onUnmounted
errorCaptured -> onErrorCaptured

6. Composition中provide和inject使用

》父子组件:通过props,$emit,【$root,$parent,$children】

》非父子组件:Vuex实现,父子层层传递、$ref

》Vue官网建议,在正常情况下,这两种方式已经能满足绝大多数甚至所有的业务需求,对于应用程序代码应优先使用它们处理。

》provide/inject这对选项允许一个祖先组件向其所有子孙后代组件注入一个依赖,不论组件层次有多深,并在起上下游关系成立的时间里始终生效。

》provide就相当于加强版父组件prop,可以跨越中间组件,inject就相当于加强版子组件的props

首先我们来看一下在data中使用的provide和inject:

//父组件RootApp中
import TwoComp from '../components/TwoComp.vue'
export default {
  components: { TwoComp },
  name:'RootApp',
  data() {
    return {
      title:'这是根组件提供的数据'
    }
  },
  provide(){
    return{
      title:this.title
    }
  }
}


//接下来我们跨过子组件,来到孙子组件ThreeComp中接收
export default {
  name:'ThreeComp',
  inject:['title']
}

这种方式存在一个问题:子组件接收的数据不是响应式的

下面来看在setup中使用响应式数据进行provide和inject传值:

//父组件RootApp中响应式数据
import TwoComp from '../components/TwoComp.vue'
import {provide, ref, reactive, toRefs} from 'vue'
export default {
  components: { TwoComp },
  name:'RootApp',
  setup() {
    let title=ref('这是根组件提供的数据')    
    const user = reactive({
      name:'abc',
      age:18
    })  
    provide('title',title)
    provide('user',user)
    return{
      title,
      ...toRefs(user)
    }
  }
}


//孙子组件ThreeComp
import { inject } from 'vue'
export default {
  name:'ThreeComp',
  // inject:['title']
  setup() {
    let title=inject('title')
    let user=inject('user')
    return{
      title,
      user
    }
  }
}

并且使用provide和inject传值是可逆的,当我们改变子组件接收的值,父组件也会同步变化

更多API的使用可以参考官方文档:https://vue3js.cn/vue-composition-api

三. Composition API处理路由

》setup和Vue的Composition API的引入,开辟了新的可能性,但为了能够充分利用Vue Router的潜力,我们将需要使用一些新功能来替换对访问this和组件内导航保护的使用。

》由于我们无权访问setup的内部this,因此无法直接访问this.$router或this.$route了,相反,我们使用useRouter和useRoute函数。

》请注意,我们仍然可以访问$router和$route在模板中,因此无需在router或之route内返回setup。

示例:选择左侧菜单,将菜单项的索引传给右侧界面显示出来

1. 创建一个RouterApi页面作为主页,同时创建另一个页面MyPage显示右侧的页面,配置路由,将MyPage嵌套在主页里(路由配置参考:Router路由详解_m0_50744582的博客-CSDN博客)

{
    path: '/routerapi',
    name: 'RouterApi',
    // route level code-splitting
    // this generates a separate chunk (about.[hash].js) for this route
    // which is lazy-loaded when the route is visited.
    component: () => import(/* webpackChunkName: "about" */ '../views/RouterApi.vue'),
    children:[
      {
        path:'page',
        component: () => import('../views/MyPage.vue'),
      },
      {
        path:'page/:id',
        component: () => import('../views/MyPage.vue'),
      }
    ]
  },

然后在主页的的data中定义一些数据并遍历显示出来,作为菜单项,并向访问的页面传参





2.在Mypage页中就可以直接使用$route访问传过来的参数

运行界面如下:

vue3.0 - Composition API_第1张图片

但是一般插值中我们只写一些简单的数据,如果要将传递过来的参数进一步处理呢?在原来的vue中我们可以定义计算属性,通过this.$route来访问参数

在组合API的setup函数中通过route.params.id可以访问,但是存在一个问题,setup函数只执行一次,接收到的参数不会变化,这时我们想到了监听器

我们可以创建一个监听器来监听当前路由的参数,使用useRoute函数对其进行操作:



接下来我们看一下useRouter函数的使用:

1. 在主页写两个跳转链接

文章一

2. 新建一个MyArticle页面,用userRouter进行页面跳转



》导航守卫:
  -    尽管您仍可以将组件内导航保护与setup功能结合使用,但Vue Router会将更新和离开提供CompositionAPI函数:
  -    onBeforeRouteLeave((to, from) => {})
  -    onBeforeRouteUpdate(async (to, from) =>{})

onBeforeRouteLeave 

    onBeforeRouteLeave((to,from)=>{
      let answer=window.confirm(`你确定要从${from.fullPath}到${to.fullPath}吗`)
      if(!answer) return false
    })

更多可以参考官网:Vue Router and the Composition API | Vue Router

四. Composition API结合vuex使用

因为无法访问setup的内部this,所以我们使用useStore函数。

vuex参考:Vuex五大核心概念_m0_50744582的博客-CSDN博客_vuex的五个属性

定义两个num,对num分别进行状态管理,先来温习一下原代码:

//index.js
import { createStore } from 'vuex'

export default createStore({
  state: {
    num1:11,
    num2:22
  },
  getters:{
    double1(state){
      return state.num1*2
    }
  },
  mutations: {
    changenum1(state,payload){
      state.num1=payload;
    }
  },
  actions: {
    timecnum1({commit,state}){
      setTimeout(()=>{
        commit('changenum1',44)
      },3000)
    }
  },
  modules: {
  }
})



 当使用组合API结合vuex时,所有内容都写在setup函数中,以下是完整代码:

// index.js
import { createStore } from 'vuex'

export default createStore({
  state: {
    num1:11,
    num2:22
  },
  getters:{
    double1(state){
      return state.num1*2
    },
    double2(state){
      return state.num2*2
    }
  },
  mutations: {
    changenum1(state,payload){
      state.num1=payload;
    },
    changenum2(state,payload){
      state.num2=payload;
    }
  },
  actions: {
    timecnum1({commit,state}){
      setTimeout(()=>{
        commit('changenum1',44)
      },3000)
    },
    timecnum2({commit,state}){
      setTimeout(()=>{
        commit('changenum2',55)
      },3000)
    }
  },
  modules: {
  }
})



更多可以去官网学习:Composition API | Vuex

你可能感兴趣的:(vue生态,vue.js,前端,javascript)