组合式API
1.前言
在Vue3中,有一项对于开发者来说非常有意义的更新 — 组合式API。
在开始介绍之前,需要注意两点:
- 选项式API和组合式API是两种不同的风格,但它们不是互斥而是并存的!
- 在需要大量逻辑组合的场景,往往使用组合式API实现
2.组合式API与选项式API
首先是选项式API,基于这种风格开发的Vue应用如左图所示:
- 各个选项都有固定的书写位置,如:data — 响应式数据、method — 写方法函数…对于一个功能模块,其相关逻辑的代码是分散的。
- 优点在于:代码结构清晰。
- 缺点在于:代码组织性差,相似的逻辑代码不方便复用,逻辑复杂时,代码庞大,同一功能模块的上下文代码难以关联。
其次是组合式API,基于这种风格开发的Vue应用如右图所示:
- 特定功能模块的相关代码都可以写在一块儿,同一功能的代码集中。
- 优点在于:
- 可以快速定位到某个功能模块的相关代码
- 当逻辑复杂、代码量较大时,可以进行逻辑拆分、功能封装
当逻辑较为复杂时,我们可以进行功能的抽象和拆分,如下图:
2.1 案例
光看图只能做到简单了解,下面我们通过一个具体的实例来体会一下两种开发风格的区别:
这里有两个独立的功能:
- 点击按钮控制div的显示与隐藏
- 点击按钮控制div内字体颜色的变化
2.3 利用选项式API实现
<template>
<div>
<!-- 功能一模板 -->
<button @click="show">显示</button>
<button @click="hide">隐藏</button>
<div v-if="showDiv">一个被控制显隐的div</div>
</div>
<div>
<!-- 功能二模板 -->
<button @click="changeRed">红色</button>
<button @click="changeYellow">蓝色</button>
<div :style="`color:${fontColor}`">一个被控制字体颜色的的div</div>
</div>
</template>
<script>
export default {
name: 'App',
data() {
return {
showDiv: true, // 功能一数据
fontColor: '' // 功能二数据
}
},
methods: {
// 功能一方法
show() {
this.showDiv = true
},
hide() {
this.showDiv = false
},
// 功能二方法
changeRed() {
this.fontColor = 'red'
},
changeYellow() {
this.fontColor = 'blue'
}
}
}
</script>
2.4 利用组合式API实现
<template>
<div>
<!-- 功能一模板 -->
<button @click="show">显示</button>
<button @click="hide">隐藏</button>
<div v-if="showDivFlag">一个被控制显隐的div</div>
</div>
<div>
<!-- 功能二模板 -->
<button @click="changeRed">红色</button>
<button @click="changeBlue">蓝色</button>
<div :style="`color:${fontColor}`">一个被控制字体颜色的的div</div>
</div>
</template>
<script>
import { ref } from 'vue'
export default {
name: 'App',
setup() {
// 功能一
const showDivFlag = ref(true)
function show() {
showDivFlag.value = true
}
function hide() {
showDivFlag.value = false
}
// 功能二
const fontColor = ref('')
function changeRed() {
fontColor.value = 'red'
}
function changeBlue() {
fontColor.value = 'blue'
}
return { showDivFlag, show, hide, fontColor, changeRed, changeBlue }
}
}
</script>
接下来我们对上面的代码进行一下优化:
<script>
import { ref } from 'vue'
function useShow() {
const showDivFlag = ref(true)
function show() {
showDivFlag.value = true
}
function hide() {
showDivFlag.value = false
}
return { showDivFlag, show, hide }
}
function useColor() {
const fontColor = ref('')
function changeRed() {
fontColor.value = 'red'
}
function changeBlue() {
fontColor.value = 'blue'
}
return { fontColor, changeRed, changeBlue }
}
export default {
name: 'App',
setup() {
// 功能一
const { showDivFlag, show, hide } = useShow()
// 功能二
const { fontColor, changeRed, changeBlue } = useColor()
return { showDivFlag, show, hide, fontColor, changeRed, changeBlue }
}
}
</script>
通过定义功能函数,把俩个功能相关的代码各自抽离到一个独立的小函数中,然后通过在 setup 函数中再把俩个小功能函数组合起来,这样一来,我们既可以把 setup 函数变得更为简单清晰,又可以方便维护快速定位功能位置。
当然,这里只是一个小示例而已,在真实的开发过程中,对于多个不同且彼此独立的功能,往往会将其抽象为几个组件,然后分别在对应的组件中编写其内部逻辑代码,最后在父页面中,引入它们的数据、方法,来实现整体需求。
不过,这里并没有涉及到组合式API中的细节,仅仅是进行一个整体的体会。
下面的部分会对组合式API的基础进行简要的介绍。
3.组合式API基础
3.1 setup函数
setup
函数是一个新的组件选项,作为组件中组合式API的起点(入口)setup
中不能使用this
,this
指向undefined
setup
函数只会在组件初始化的时候执行一次setup
函数在beforeCreate
生命周期钩子执行之前执行
不过现在使用更多的应该是<script setup>
语法糖,即下面这种形式:
<script setup>
// 变量
const msg = 'Hello!'
// 函数
function log() {
console.log(msg)
}
//...
</script>
里面的代码会被编译成组件setup()函数的内容。这意味着与普通的
<script>
只在组件被首次引入的时候执行一次不同,<script setup>
中的代码会在每次组件实例被创建的时候执行。
当使用<script setup>
的时候,任何在<script setup>
声明的顶层的绑定 (包括变量,函数声明,以及 import 引入的内容) 都能在模板中直接使用!
3.2 ref和reactive函数
它们均用于将普通数据转换为响应式数据。
具体内容和区别等可以看这篇文章:【Vue3基础】ref 和 reactive
3.3 toRef函数
toRef
方法用于转换响应式对象中的某一个属性为单独的响应式数据,并且,这两者的数据是关联的!
<script setup>
import { reactive, toRef } from 'vue'
const obj = reactive({
name: 'xiaoming',
info: {
age: '10',
sex: 'male',
},
})
// 如果需要使用info数据,不能直接解构!例如:const { name } = obj
// 因为直接解构出来的数据不再具有响应式的特性,会变成一个普通数据!
// 隐藏这里就需要用toRef函数
const name = toRef(obj, 'name')
const info = toRef(obj, 'info')
// 更新该数据,原本的响应式对象中的对应属性也会变化
const update = () => {
name.value = 'xiaowang';
info.value.age = '14';
}
return { name, updateName };
</script>
3.4 toRefs函数
上面说到过,对于响应式对象,如果将其解构或展开,会让数据丢失响应式的能力!
而在某些场景中,解构或展开该对象又是不可避免的,因此,为了解决这个问题,引入了toRefs
函数,使用该函数可以保证该对象展开的每个属性都是响应式的。
<script setup>
import { reactive, toRefs } from 'vue'
const obj = reactive({
name: 'xiaoming',
info: {
age: '10',
sex: 'male',
},
})
// 这里需要将obj对象的各属性返回,因此使用扩展运算符是最为简单方便的
// 但扩展运算符会导致展开的数据失去响应式特性,所以需要配置toRefs函数使用
return { ...toRefs(obj) };
</script>
3.5 生命周期
需要注意的是,在组合式API中,可以多次使用同一个生命周期钩子函数,其执行顺序和书写顺序相同。
3.5 补充
首先是关于v-model
语法糖调整:
- 在vue2.0中v-mode语法糖简写的代码
<Son :value="msg" @input="msg=$event" />
- 在vue3.0中v-model语法糖有所调整:
<Son :modelValue="msg" @update:modelValue="msg=$event" />
在实际应用中,常常用在父子组件通信中,通过在子组件身上绑定:modelValue="msg"
来传递数据,子组件中则在适当的地方用emit('update:modelValue', xxx)
更新数据(也可以理解为向父组件传递数据)。
其次是一些其它内容:
Vue3中的 computed、watch 和 watchEffect — 【Vue3基础】Vue3中的 computed、watch 和 watchEffect
Vue3 组件通信 — 【Vue3基础】Vue3 组件间通信
Vue3 Provide/Inject — 【Vue3基础】依赖注入 — Provide/Inject
Vue3 mixins —