vue3进阶-组合式API

5,841

前言:

最近刚弄明白什么是组合式API,什么是选项式API,使用vue2.x这么多年,居然不知道一直在使用的居然是选项式API,以此文对这两个知识点做个总结,纪念自己的无知。

选项式API VS 组合式API

1.vue2中的选项式 API (options API)

页面使用methods、watch、computed等来处理页面逻辑,vue2中的常规格式,不做过多说明。

export default{
    data(){
        
    },
    //方法
    methods:{
        
    },
    //观察属性
    watch:{
        
    },
    //计算属性
    computed:{
        
    }
}

为什么使用组合式API

选项式 API,看起来结构很清晰,当随着业务功能不断增加,代码不断迭代,methods、watch、computed中的代码会不断增加,可能一个vue中包含几个功能,这些功能在methods等内是零散的,当进行bug修复或者添加新功能时带来的工作量会很大,基于这些问题,vue3的组合式API应运而生。

2. vue3中的组合式API

引入vue官方示例,setup内的部分为组合式API

export default {
  props: {
    user: { type: String }
  },
  setup(props) {
    console.log(props) // { user: '' }

    return {} // 这里返回的任何内容都可以用于组件的其余部分
  }
  // 组件的“其余部分”
}

setup参数

setup接收2个参数

1.props

父组件传入的props

示例:
export default {
  props: {
    title: String
  },
  setup(props) {
    console.log(props.title) //父组件传入的title值
  }
}

2.context

context包含3个参数,attrsslotsemit

示例:
export default {
  setup(props, context) {
    // Attribute (非响应式对象)
    console.log(context.attrs) //包含除props以外的参数,与vue2中的this.$attrs功能一致

    // 插槽 (非响应式对象)
    console.log(context.slots)//包含插槽,与this.$slots功能一致

    // 触发事件 (方法)
    console.log(context.emit)//触发事件,与this.$emit方法一致
  }
}

前置知识点:

vue3支持tree-shaking(死代码消除),因此vue2中挂载在vue上的全局方法现在必须单独引入才能使用。

vue 2.x
this.$nextTich(()=>{
    
});

vue 3
import { nextTick } from 'vue'
nextTick(()=>{
    
})

setup什么时候触发

在组件创建之前执行,props被解析,然后执行setup并将props传入方法,由于执行setup时组件实例尚未创建,所以setup内无法使用this,并且无法访问methodswatchcomputed内的方法及属性。

setup使用

1. 响应式变量

setup内使用ref函数实现变量的响应式,并使用return返回变量,供给模板内使用。

未使用ref

示例:
<template>
  <div>name:{{name}}</div>
  <button @click="updateName">修改姓名</button>
</template>

<script>
export default {
  name: "test",
  setup(){
    let name = '';
    let updateName = ()=>{
      name = 'xiaoming';
      console.log("修改名字",name)
    }
    return {name,updateName}
  }
}
</script>

<style scoped>
</style>

由于变量是未响应的,所以点击修改触发方法,修改后的值并没有更新到dom中。

使用ref

示例:
<template>
  <div>name:{{name}}</div>
  <button @click="updateName">修改姓名</button>
</template>

<script>
import {ref} from 'vue'
export default {
  name: "test",
  setup(){
    let name = ref('')
    console.log(name); //{value:''}
    let updateName = ()=>{
      name.value = 'xiaoming';
      console.log("修改名字",name)
    }
    return {name,updateName}
  }
}
</script>

<style scoped>
</style>

使用ref方法定义变量后,返回的是一个对象,如果需要修改变量的值需要使用变量.value修改变量的值。

2. setup内使用watch

示例:
<template>
  <div>name:{{name}}</div>
  <button @click="updateName">修改姓名</button>
</template>

<script>
import {ref,watch} from 'vue'
export default {
  name: "test",
  setup(){
    let name = ref('')
    console.log(name);
    let updateName = ()=>{
      name.value = 'xiaoming';
      console.log("修改名字",name)
    }
    //计算属性观察name变化
    watch(name,(newVal,oldVal)=>{
      console.log("newVal",newVal) //xiaoming
      console.log("oldVal",oldVal) // ''
    })
    return {name,updateName}
  }
}
</script>

<style scoped>
</style>

3. setup内使用computed

示例:
<template>
  <div>name:{{name}}</div>
  <div>{{nowName}}</div>
  <button @click="updateName">修改姓名</button>
</template>

<script>
import {ref,computed} from 'vue'
export default {
  name: "test",
  setup(){
    let name = ref('')
    console.log(name);
    let updateName = ()=>{
      name.value = 'xiaoming';
      console.log("修改名字",name)
    }
    //使用计算属性返回
    let nowName = computed(()=>{
      return `现在的名字是:${name.value}`
    })
    return {name,updateName,nowName}
  }
}
</script>

<style scoped>
</style>

4. setup内触发生命周期函数

选项式API与对应的组合式API生命周期

选项式 APIHook inside setup
beforeCreateNot needed*
createdNot needed*
beforeMountonBeforeMount
mountedonMounted
beforeUpdateonBeforeUpdate
updatedonUpdated
beforeUnmountonBeforeUnmount
unmountedonUnmounted
errorCapturedonErrorCaptured
renderTrackedonRenderTracked
renderTriggeredonRenderTriggered

选项式API的生命周期钩子函数与组合式API的触发时机一致,书写方法有区别,下面列了几个常用的生命周期钩子函数的定义方式。

示例:
<template>
  <div>name:{{name}}</div>
  <div>{{nowName}}</div>
  <button @click="updateName">修改姓名</button>
</template>

<script>
<!--引入所需内置函数-->
import {onBeforeMount,onMounted,onBeforeUpdate,onUpdated,ref} from 'vue'
export default {
  name: "test",
  setup(){
    //挂载前
    onBeforeMount(()=>{
      console.log("onBeforeMount")
    })
    //挂载后
    onMounted(()=>{
      console.log("onMounted")
    })
    //修改前
    onBeforeUpdate(()=>{
      console.log("onBeforeUpdate")
    })
    //修改后
    onUpdated(()=>{
      console.log("onUpdated")
    })
    let name = ref('')
    console.log(name);
    let updateName = ()=>{
      name.value = 'xiaoming';
      console.log("修改名字",name)
    }
    return {name,updateName}
  }
}
</script>

<style scoped>
</style>

5. 依赖注入( Provide / Inject)

父组件将参数注入provide,子、孙组件使用inject,这里使用官方示例

父组件:

<template>
  <MyMarker />
</template>

<script>
import { provide } from 'vue'
import MyMarker from './MyMarker.vue

export default {
  components: {
    MyMarker
  },
  setup() {
    //向子组件及孙子组件注入两个参数,分别是地址及坐标,一个变量,一个对象
    provide('location', 'North Pole')
    provide('geolocation', {
      longitude: 90,
      latitude: 135
    })
  }
}
</script>

子组件:

<script>
import { inject } from 'vue'

export default {
  setup() {
    //引入父组件注入的变量
    const userLocation = inject('location', 'The Universe')
    const userGeolocation = inject('geolocation')
    
    return {
      userLocation,
      userGeolocation
    }
  }
}
</script>

6. 依赖注入响应式

上面注入的参数如果发生变动,子组件内引入的参数并不会变化,接下来将参数动态化。

子组件

<template>
  <MyMarker />
</template>

<script>
import { provide, reactive, ref } from 'vue'
import MyMarker from './MyMarker.vue

export default {
  components: {
    MyMarker
  },
  setup() {
    <!--ref上面已经介绍过-->
    const location = ref('North Pole')
    <!--reactive用法与ref相同,不过ref针对的是基本数据类型,而reactive针对的是引用数据类型-->
    const geolocation = reactive({
      longitude: 90,
      latitude: 135
    })

    provide('location', location)
    provide('geolocation', geolocation)
  }
}
</script>

如果要确保注入的参数不被子组件修改,可以在注入时使用readonly避免子组件修改参数

父组件

<template>
  <MyMarker />
</template>

<script>
import { provide, reactive, readonly, ref } from 'vue'
import MyMarker from './MyMarker.vue

export default {
  components: {
    MyMarker
  },
  setup() {
    const location = ref('North Pole')
    const geolocation = reactive({
      longitude: 90,
      latitude: 135
    })
    <!--readonly使子组件无法修改父组件注入的参数-->
    provide('location', readonly(location))
    provide('geolocation', readonly(geolocation))
  }
}
</script>

结语:

至此组合式API基本介绍完毕,在开发过程中还是需要自己选择是使用选项式API还是使用组合式API来开发,需要结合自己的使用场景做出选择。