前言
9月18日,Vue 3.0正式发布。其中,Composition API可谓是新版本的一大亮点。本文将通过一个小demo简单介绍Composition API的主要功能和使用方式。同时,我们将使用Vue团队发布的一个新的构建工具Vite作为这个小demo的构建工具。
Composition API是什么?
Vue 3的官方文档现在可以在v3.vuejs.org中查看(目前还未上线中文文档)。在文档中找到Composition API,可以查看该API的详细介绍。通过官方文档我们知道,Composition API主要是为了解决规模较大的Vue组件的可维护性、灵活性和可复用性的问题。传统的Vue组件将组件分成了props
、components
、data
、methods
、computed
、watch
及多个声明周期钩子等多个部分。组件的各个逻辑共能也分散在这多个部分中,当组件具有一定的规模后,代码逻辑就会出现支离破碎的情况,极大地影响了代码的可维护性等。
为了解决这个问题,Vue团队提出了将大组件中的代码按照逻辑关系组织在一起,这就促成了Composition API的诞生。当组件出现下述情况,使用Composition API可能会更好地避免问题的出现:
- 某部分代码可以在多个组件中重复利用,且想使用其他方法代替mixins或slot
- 确保类型安全(尤其是在使用TypeScript时)
搭建demo
首先我们使用Vite
构建命令初始化这个项目vue3-composition-api-demo
(顺便提一嘴,Vite
也可以用来构建react
项目)
# 初始化项目
npm init vite-app vue3-composition-api-demo
# 进入目录
cd vue3-composition-api-demo
# 安装依赖
npm install
# 运行项目
npm run dev
开发环境的项目构建速度比起webpack
是真的快了很多,得益于原生ES Module
的支持。类似的构建工具还有Snowpack,感兴趣的小伙伴可以去了解了解。
Composition API的基本使用
Composition API在Vue 3中开箱即用,如果想在Vue 2中使用Composition API,可以在项目中添加@vue/composition-api
插件。
首先我们写一些模板代码:
<template>
<div>
<p>Spaces left: {{ spacesLeft }} out of {{ capacity }}</p>
<h2>Attending</h2>
<div>
<button @click="increaseCapacity">Increase Capacity</button>
</div>
<div>
<button @click="addAttending">addAttending</button>
</div>
<div>
<button @click="sort">sort</button>
</div>
<!-- 使用vue的过渡动画组件演示,嫌麻烦的同学也可以不用(语气逐渐王刚化) -->
<transition-group name="flip-list" tag="ul" :style="{width: '200px', margin: '20px auto'}">
<li v-for="item in attending" :key="`Attending${item}`">
{{ item }}
</li>
</transition-group>
</div>
</template>
Vue模板中使用了两个data
变量capacity
和attending
,一个computed
属性spacesLeft
和几个相关的函数。
接着我们在script
标签中定义它们。我们将借助setup
方法,通过两种写法实现这些功能。
写法一
// 引入ref和computed
import { ref, computed } from "vue"
export default {
setup() {
const capacity = ref(4)
const attending = ref(["Tim", "Bob", "Joe"])
// computed计算属性的声明方式
const spacesLeft = computed(() => {
// 注意,通过ref声明的响应式数据需要通过.value拿到该值
return capacity.value - attending.value.length
})
// 代替methods声明方法
function increaseCapacity () {
capacity.value++
}
function sort () {
attending.value = lodash.shuffle(attending.value)
}
function addAttending () {
attending.value.push(randomName(lodash.random(3, 7)))
}
// 通过一个对象暴露以上变量和方法
return {
capacity,
attending,
spacesLeft,
increaseCapacity
}
}
}
其中,ref()
接收一个value值作为入参,基本类型的入参将被包裹在一个object中(如capacity
),通过proxy
实现响应式(这很Vue 3)。computed
计算属性,是通过computed()
方法声明的(全部替换成了函数式的声明),注意computed
中,我们需要调用capacity.value
和attending.value
来获取数据的值。最后,所有在template中使用的变量和方法通过return暴露出去。我们再来看看第二种写法。
写法二
import { reactive, computed, toRefs } from "vue"
export default {
// 将需要使用的变量添加到event的属性中
setup(props, context) {
const event = reactive({
capacity: 4,
attending: ['Tim', 'Bob', 'Joe'],
spacesLeft: computed(() => { return event.capacity - event.attending.length })
})
// 方法的声明方法不变
function increaseCapacity () {
event.capacity++
}
function sort () {
event.attending = lodash.shuffle(event.attending)
}
function addAttending () {
event.attending.push(randomName(lodash.random(3, 7)))
}
// toRefs()
return {...toRefs(event), increaseCapacity, addAttending, sort}
}
}
改变的地方是toRefs()
。toRefs()
方法接受一个Reactive
实例作为入参,返回一个响应式的object
。在setup()
函数体内直接通过event
属性拿到相关变量值,不需要额外加上.value
。两种写法在使用场景上略有区别。我个人更喜欢第二种写法,return
更加简洁优雅👍。
最后,这个页面看起来应该是这样的:
setup方法
我们来看看setup()
方法。
setup(props, context) {
// Attributes (Non-reactive object)
console.log(context.attrs)
// Slots (Non-reactive object)
console.log(context.slots)
// Emit Events (Method)
console.log(context.emit)
}
setup()
方法接受2个参数,第一个参数props
,顾名思义就是Vue的props
。setup()
执行时还没有vue
实例,所以在setup()
函数体内拿不到data
、computed
和methods
,所以需要借助第二个参数context
拿到vue实例相关的数据,其中包含attrs
、slots
和emit
。
setup生命周期hooks
setup()
内部有相应的vue生命周期hooks供开发者使用。官方文档有具体的罗列。
我们来对比打印一下新旧两版生命周期hooks:
import {
onBeforeMount,
onMounted,
onBeforeUpdate,
onUpdated,
onUnmounted
} from "vue"
function printLifeCycle () {
onBeforeMount(() => {console.log('onBeforeMount')})
onBeforeUpdate(() => {console.log('onBeforeUpdate')})
onMounted(() => {console.log('onMounted')})
onUpdated(() => {console.log('onUpdated')})
onUnmounted(() => {console.log('onUnmounted')})
}
export default {
// setup内部生命周期hooks
setup(props, context) {
printLifeCycle()
},
// 老生命周期hooks
beforeCreate() {
console.log('beforeCreate')
},
created() {
console.log('created')
},
beforeMount() {
console.log('beforeMount')
},
mounted() {
console.log('mounted')
},
beforeUpdate() {
console.log('beforeUpdate')
},
updated() {
console.log('updated')
}
}
setup()
内部的hooks
执行早于vue
实例上的老hooks
。打印顺序如下:
彩蛋 - Vite引入第三方库
细心的同学应该发现了我在demo中使用了lodash
的shuffle
和random
。在Vite
中引入部分未使用ES Module的依赖会报错找不到该依赖。需要注意,在vite.config.js
中的optimize
配置下加入该依赖:
module.exports = {
optimizeDeps: {
include: ["lodash"]
}
}
在引入lodash
时,需要注意,这样会报错:
import { random, shuffle } from 'lodash'
这样也会报错:
import random from 'lodash/random'
import shuffle from 'lodash/shuffle'
所以目前只能够乖乖全部引入lodash
:
import lodash from 'lodash'
算是ES Module的一个小坑吧,相关问题可以参考这篇博客vite 尝试和入门。